// import {useSelector} from "react-redux";

export const AxisType = {SQUARE: {}, NORMAL: {}, NONE: {}};
export const BorderStyle = {SOLID: {}, DOTTED: {}, DASHED: {}};

/**
 * 所有 canvas 插件的父类
 *
 * @class
 */
class MyCanvas {
  constructor(id, initParam = {}) {
    this.container = document.getElementById(id);
    this.canvas = document.createElement("canvas");
    this.canvas.setAttribute("id", id + "ImgUrl");
    this.context = this.canvas.getContext("2d");
    this.container.append(this.canvas);

    this.drawWholeCanvasBackgroundMode = Utils.valueOf(initParam.drawWholeCanvasBackgroundMode, false);
    this.wholeCanvasBackground = Utils.valueOf(initParam.wholeCanvasBackground, "#FFFFFF");
    this.paddingTop = Utils.valueOf(initParam.paddingTop, 0);
    this.paddingBottom = Utils.valueOf(initParam.paddingBottom, 0);
    this.paddingLeft = Utils.valueOf(initParam.paddingLeft, 0);
    this.paddingRight = Utils.valueOf(initParam.paddingRight, 0);
    this.maxY = Utils.valueOf(initParam.maxY, Infinity);
    this.minY = Utils.valueOf(initParam.minY, 0);
    this.maxX = Utils.valueOf(initParam.maxX, Infinity);
    this.minX = Utils.valueOf(initParam.minX, 0);

    this.refreshing = true;
  }

  __animation() {
    if (!this.refreshing) {
      return;
    }
    this.refresh();
    requestAnimationFrame(() => this.__animation());
  }

  /**
   * 重新更新 canvas 的大小，一般在父容器大小发生变化之后调用
   */
  resize(widthScale = 1, heightScale = 1) {
    this.canvas.width = this.width = this.container.clientWidth * widthScale;
    this.canvas.height = this.height = this.container.clientHeight * heightScale;

    this.left = this.paddingLeft;
    this.right = this.width - this.paddingRight;
    this.top = this.paddingTop;
    this.bottom = this.height - this.paddingBottom;

    this.contentHeight = this.bottom - this.top;
    this.contentWidth = this.right - this.left;

    this.coeY = this.contentHeight / (this.maxY - this.minY);
    this.coeX = this.contentWidth / (this.maxX - this.minX);
  }

  /**
   * 刷新 canvas，一般在增删改数据之后可调用
   */
  refresh() {
    this.context.clearRect(0, 0, this.width, this.height);
    if (this.drawWholeCanvasBackgroundMode) {
      this.drawWholeCanvasBackground();
    }
  }

  /**
   * 清除数据，并更新绘图区域，一般用于重新初始化
   */
  clearData() {
  }

  /**
   * 销毁canvas元素
   */
  destroyCanvas() {
    this.refreshing = false;
    this.clearData();
    this.canvas.remove();
  }

  /**
   * 给整个 canvas 绘制背景色。
   */
  drawWholeCanvasBackground() {
    this.context.fillStyle = this.wholeCanvasBackground;
    this.context.fillRect(0, 0, this.width, this.height);
  }

  setDrawWholeCanvasBackgroundMode(drawWholeCanvasBackgroundMode) {
    this.drawWholeCanvasBackgroundMode = Utils.valueOf(drawWholeCanvasBackgroundMode, false);
  }

  setWholeCanvasBackground(wholeCanvasBackground) {
    this.wholeCanvasBackground = Utils.valueOf(wholeCanvasBackground, "#FFFFFF");
  }

  setPaddingTop(paddingTop) {
    this.paddingTop = Utils.valueOf(paddingTop, 0);
    this.resize();
  }

  setPaddingBottom(paddingBottom) {
    this.paddingBottom = Utils.valueOf(paddingBottom, 0);
    this.resize();
  }

  setPaddingLeft(paddingLeft) {
    this.paddingLeft = Utils.valueOf(paddingLeft, 0);
    this.resize();
  }

  setPaddingRight(paddingRight) {
    this.paddingRight = Utils.valueOf(paddingRight, 0);
    this.resize();
  }

  setMaxY(maxY) {
    this.maxY = Utils.valueOf(maxY, Infinity);
    this.resize();
  }

  setMinY(minY) {
    this.minY = Utils.valueOf(minY, 0);
    this.resize();
  }

  setMaxX(maxX) {
    this.maxX = Utils.valueOf(maxX, Infinity);
    this.resize();
  }

  setMinX(minX) {
    this.minX = Utils.valueOf(minX, 0);
    this.resize();
  }

  /**
   * 根据数据获取图上位置
   *
   * @param {number} x
   * @returns {number}
   */
  getX(x) {
    return this.left + (x - this.minX) * this.coeX;
  }

  /**
   * 根据数据获取图上位置
   *
   * @param {number} y
   * @returns {number}
   */
  getY(y) {
    return this.bottom - (y - this.minY) * this.coeY;
  }

  /**
   * 判断 x 轴方向两点是否距离相近，用于鼠标移动捕捉
   *
   * @param {number} a - x 轴实际值
   * @param {number} b - 像素点值
   * @param {number} c - 两像素点差值
   * @return {boolean}
   */
  nearX(a, b, c = 5) {
    return Math.abs(this.getX(a) - b) < c;
  }

  /**
   * 判断 y 轴方向两点是否距离相近，用于鼠标移动捕捉
   *
   * @param {number} a - y 轴实际值
   * @param {number} b - 像素点值
   * @param {number} c - 两像素点差值
   * @return {boolean}
   */
  nearY(a, b, c = 5) {
    return Math.abs(this.getY(a) - b) < c;
  }

  getDataURL() {
    return this.canvas.toDataURL();
  }

  getBase64() {
    return this.getDataURL().split(",")[1];
  }
}

/**
 * Protocol 相关的 canvas 插件
 *
 * @class
 */
export class ProtocolCanvas extends MyCanvas {
  static HorizontalMode = {AUTO: "auto", FIXED: "fixed"};

  constructor(id, initParam = {}) {
    super(id, initParam);

    this.widthLeft = Utils.valueOf(initParam.widthLeft, 100);
    this.widthRight = Utils.valueOf(initParam.widthRight, 100);
    this.lineWidthNormal = Utils.valueOf(initParam.lineWidthNormal, 3);
    this.lineWidthBold = Utils.valueOf(initParam.lineWidthBold, 8);
    this.lineColor = Utils.valueOf(initParam.lineColor, "#000000");
    this.drawHorizontalMode = Utils.valueOf(initParam.drawHorizontalMode, ProtocolCanvas.HorizontalMode.FIXED);
    this.drawBackgroundMode = Utils.valueOf(initParam.drawBackgroundMode, true);
    this.background = Utils.valueOf(initParam.background, "#EEEEEE");
    this.drawTemperatureMode = Utils.valueOf(initParam.drawTemperatureMode, false);
    this.temperaturePaddingRight = Utils.valueOf(initParam.temperaturePaddingRight, 5);
    this.temperatureBold = Utils.valueOf(initParam.temperatureBold, false);
    this.temperatureSize = Utils.valueOf(initParam.temperatureSize, "21px");
    this.temperatureFamily = Utils.valueOf(initParam.temperatureFamily, "Arial");
    this.temperatureColor = Utils.valueOf(initParam.temperatureColor, "#000000");
    this.drawTitleMode = Utils.valueOf(initParam.drawTitleMode, false);
    this.titlePaddingTop = Utils.valueOf(initParam.titlePaddingTop, 10);
    this.titleBold = Utils.valueOf(initParam.titleBold, true);
    this.titleSize = Utils.valueOf(initParam.titleSize, "21px");
    this.titleFamily = Utils.valueOf(initParam.titleFamily, "Arial");
    this.titleColor = Utils.valueOf(initParam.titleColor, "#5599FF");
    this.drawStageSeparationLineMode = Utils.valueOf(initParam.drawStageSeparationLineMode, false);
    this.stageSeparationLineColor = Utils.valueOf(initParam.stageSeparationLineColor, "#5599FF");
    this.stageSeparationLineWidth = Utils.valueOf(initParam.stageSeparationLineWidth, 4);
    this.drawStepSeparationLineMode = Utils.valueOf(initParam.drawStepSeparationLineMode, false);
    this.stepSeparationLineColor = Utils.valueOf(initParam.stepSeparationLineColor, "#5599FF");
    this.stepSeparationLineWidth = Utils.valueOf(initParam.stepSeparationLineWidth, 2);
    this.drawRealtime = Utils.valueOf(initParam.drawRealtime, false);
    this.lineColorRealtime = Utils.valueOf(initParam.lineColorRealtime, "#FF0000");
    this.progressBarPaddingTop = Utils.valueOf(initParam.progressBarPaddingTop, 45);
    this.progressBarPaddingHorizontal = Utils.valueOf(initParam.progressBarPaddingHorizontal, 20);
    this.progressBarBorderColor = Utils.valueOf(initParam.progressBarBorderColor, "#000000");
    this.progressBarColor = Utils.valueOf(initParam.progressBarColor, "#5599FF");
    this.progressBarCycleColor = Utils.valueOf(initParam.progressBarCycleColor, "#FFFFFF");
    this.progressBarHeight = Utils.valueOf(initParam.progressBarHeight, 16);
    this.drawCameraMode = Utils.valueOf(initParam.drawCameraMode, false);
    this.cameraColor = Utils.valueOf(initParam.cameraColor, "#00FF00");
    this.cameraCenterColor = Utils.valueOf(initParam.cameraCenterColor, "#FFFFFF");
    this.cameraSize = Utils.valueOf(initParam.cameraSize, 1);
    this.cameraPadding = Utils.valueOf(initParam.cameraPadding, 20);
    this.drawTimeTemperatureMode = Utils.valueOf(initParam.drawTimeTemperatureMode, false);
    this.timeTemperatureBold = Utils.valueOf(initParam.timeTemperatureBold, false);
    this.timeTemperatureSize = Utils.valueOf(initParam.timeTemperatureSize, "18px");
    this.timeTemperatureFamily = Utils.valueOf(initParam.timeTemperatureFamily, "Arial");
    this.timeTemperatureColor = Utils.valueOf(initParam.timeTemperatureColor, "#000000");
    this.timeTemperaturePaddingVertical = Utils.valueOf(initParam.timeTemperaturePaddingVertical, 10);

    this.data = [];
    this.dataCurrent = {
      step: 0,
      temperature: 0,
      percent: 0,
      text: 0
    };
    this.dataCamera = new Set();
    this.dataTemperaturesList = [];
    this.dataTimeTemperatureList = [];
    this.dataTitles = [];
    this.dataStages = Utils.valueOf(initParam.stages, []);
    this.dataCycles = Utils.valueOf(initParam.cycles, []);
  }

  /**
   * 初始化组件
   *
   * @param {string} id 父容器的 id
   * @param {Object} [initParam] - 初始化参数
   * @param {number} [initParam.widthLeft] - 每个 step 左半部分宽度，默认值为 100。在 drawHorizontalMode 为
   *   {@link HorizontalMode.FIXED} 时生效
   * @param {number} [initParam.widthRight] - 每个 step 右半部分宽度，默认值为 100。在 drawHorizontalMode 为
   *   {@link HorizontalMode.FIXED} 时生效
   * @param {number} [initParam.lineWidthNormal] - 斜线线宽，默认值为 3。
   * @param {number} [initParam.lineWidthBold] - 横线线宽，默认值为 8。
   * @param {number} [initParam.lineColor] - 固定线颜色，默认值为 "#000000"。
   * @param {number} [initParam.paddingTop] - 绘制区域距 canvas 顶部距离，默认值为 0。
   * @param {number} [initParam.paddingBottom] - 绘制区域距 canvas 底部距离，默认值为 0。
   * @param {number} [initParam.paddingLeft] - 绘制区域距 canvas 左边距离，默认值为 0。
   * @param {number} [initParam.paddingRight] - 绘制区域距 canvas 右边距离，默认值为 0。
   * @param {string} [initParam.drawHorizontalMode] - 横向绘制模式, 值为 {@link HorizontalMode.FIXED}(固定宽度)或
   *   {@link HorizontalMode.AUTO}(自动计算宽度)。
   * @param {boolean} [initParam.drawBackgroundMode] - 是否绘制背景色，默认值为 true。
   * @param {string} [initParam.background] - 背景色，默认值为 "#EEEEEE"。
   * @param {boolean} [initParam.drawWholeCanvasBackgroundMode] - 是否绘制整个 canvas 的背景色，默认值为 false。
   * @param {string} [initParam.wholeCanvasBackground] - 整个 canvas 的背景色，默认值为 "#FFFFFF"。
   * @param {boolean} [initParam.drawTemperatureMode] - 是否绘制温度数值，默认值为 false。
   * @param {number} [initParam.temperaturePaddingRight] - 绘制温度距第一个 step 顶部距离，默认值为 5。
   * @param {boolean} [initParam.temperatureBold] - 绘制温度是否为粗体，默认值为 false。
   * @param {string} [initParam.temperatureSize] - 绘制温度字号，默认值为 "21px"。
   * @param {string} [initParam.temperatureFamily] - 绘制温度字体，默认值为 "Arial"。
   * @param {string} [initParam.temperatureColor] - 绘制温度颜色，默认值为 "#000000"。
   * @param {number} [initParam.titlePaddingTop] - 绘制温度距 canvas 顶部距离，默认值为 10。
   * @param {boolean} [initParam.titleBold] - 每个 stage 的标题是否为粗体，默认值为 true。
   * @param {string} [initParam.titleSize] - 每个 stage 的标题字号，默认值为 "21px"。
   * @param {string} [initParam.titleFamily] - 每个 stage 的标题字体，默认值为 "Arial"。
   * @param {string} [initParam.titleColor] - 每个 stage 的标题颜色，默认值为 "#5599FF"。
   * @param {boolean} [initParam.drawStageSeparationLineMode] - 是否绘制每个 stage 之间分割线，默认值为 false。
   * @param {string} [initParam.stageSeparationLineColor] - 每个 stage 之间分割线颜色，默认值为 "#5599FF"。
   * @param {number} [initParam.stageSeparationLineWidth] - 每个 stage 的标题颜色，默认值为 4。
   * @param {boolean} [initParam.drawStepSeparationLineMode] - 是否绘制每个 step 之间分割线，默认值为 false。
   * @param {string} [initParam.stepSeparationLineColor] - 每个 stage 的标题颜色，默认值为 "#5599FF"。
   * @param {number} [initParam.stepSeparationLineWidth] - 每个 stage 的标题颜色，默认值为 2。
   * @param {number} [initParam.maxY] - 设置绘制数据的最大值（纵轴），默认值为 Infinity。
   * @param {number} [initParam.minY] - 设置绘制数据的最小值（纵轴），默认值为 0。
   * @param {number} [initParam.maxX] - 设置绘制数据的最大值（横轴），默认值为 Infinity。
   * @param {number} [initParam.minX] - 设置绘制数据的最小值（横轴），默认值为 0。
   * @param {boolean} [initParam.drawRealtime] - 是否绘制实时数据，默认值为 false。
   * @param {string} [initParam.lineColorRealtime] - 实时数据线的颜色，默认值为 "#FF0000"。
   * @param {number} [initParam.progressBarPaddingTop] - 设置进度条距 canvas 顶部距离，默认值为 45。
   * @param {number} [initParam.progressBarPaddingHorizontal] - 设置进度条距 stage 边缘距离，默认值为 20。
   * @param {string} [initParam.progressBarBorderColor] - 设置进度条边框颜色，默认值为 "#000000"。
   * @param {string} [initParam.progressBarColor] - 设置进度条内容颜色，默认值为 "#5599FF"。
   * @param {string} [initParam.progressBarCycleColor] - 设置进度条循环数文字颜色，默认值为 "#FFFFFF"。
   * @param {number} [initParam.progressBarHeight] - 设置进度条边框高度，默认值为 16。
   * @param {string} [initParam.cameraColor] - 设置相机颜色，默认值为 "#00FF00"。
   * @param {string} [initParam.cameraCenterColor] - 设置相机中间圆圈的背景色，默认值为 "#FFFFFF"。
   * @param {number} [initParam.cameraSize] - 设置相机大小，默认值为 1。
   * @param {number} [initParam.cameraPadding] - 设置相机距 step 边的距离，默认值为 20。
   * @param {boolean} [initParam.drawTimeTemperatureMode] - 是否在每个 step 上绘制温度和时间，默认值为 false。
   * @param {boolean} [initParam.timeTemperatureBold] - 每个 step 上温度和时间是否加粗，默认值为 false。
   * @param {string} [initParam.timeTemperatureSize] - 每个 step 上温度和时间字号，默认值为 “18px”。
   * @param {string} [initParam.timeTemperatureFamily] - 每个 step 上温度和时间字体，默认值为 "Arial"。
   * @param {string} [initParam.timeTemperatureColor] - 每个 step 上温度和时间颜色，默认值为 "#000000"。
   * @param {number} [initParam.timeTemperaturePaddingVertical] - 每个 step 上温度和时间距横线的距离，默认值为 10。
   * @param {number[]} [initParam.stages] - 设置每个 stage 的 step 数，1-based，默认值为空数组。
   * @param {number[]} [initParam.cycles] - 设置每个 stage 的 cycle 数，1-based，默认值为空数组。
   * @returns {ProtocolCanvas}
   */
  static init(id, initParam = {}) {
    let canvas = new ProtocolCanvas(id, initParam);
    canvas.resize();
    canvas.__animation();
    return canvas;
  }

  /** @inheritDoc */
  refresh() {
    super.refresh();
    if (this.drawBackgroundMode) {
      this.drawBackground();
    }
    if (this.drawStepSeparationLineMode) {
      __ProtocolCanvas.drawStepSeparationLine(this);
    }
    if (this.drawStageSeparationLineMode) {
      __ProtocolCanvas.drawStageSeparationLine(this);
    }
    if (this.drawTemperatureMode) {
      __ProtocolCanvas.drawTemperature(this);
    }
    if (this.drawTitleMode) {
      __ProtocolCanvas.drawTitle(this);
    }
    __ProtocolCanvas.draw(this);
    if (this.drawCameraMode) {
      __ProtocolCanvas.drawCamera(this);
    }
    if (this.drawTimeTemperatureMode) {
      __ProtocolCanvas.drawTimeTemperatures(this);
    }
    if (this.drawRealtime) {
      __ProtocolCanvas.drawProgressBar(this);
      __ProtocolCanvas.drawCurrent(this);
    }
  }

  /** @inheritDoc */
  clearData() {
    this.data.length = 0;
    this.dataCurrent.step = 0;
    this.dataCamera.clear();
    this.dataTemperaturesList.length = 0;
    this.dataTimeTemperatureList.length = 0;
    this.dataTitles.length = 0;
    this.dataStages.length = 0;
    this.dataCycles.length = 0;
  }

  /**
   * 绘制背景色，可通过修改{@link drawBackgroundMode}参数，在绘制数据之前绘制背景色。
   */
  drawBackground() {
    this.context.fillStyle = this.background;
    this.context.fillRect(this.left, this.top, this.contentWidth, this.contentHeight);
  }

  /**
   * 绘制静态数据，累加数据，清空数据请调用{@link clearData}
   *
   * @param {Number[]} data 每个 step 的温度数据
   */
  draw(data) {
    if (!Array.isArray(data)) {
      return;
    }
    data.forEach(num => typeof num === "number" && (this.data[this.data.length] = num));
  }

  /**
   * 绘制实时数据，需要将 drawRealtime 参数设置为 true 才可生效
   *
   * @param {object} current - 实时状态信息
   * @param {number} [current.step] - 当前 step， 1-based。
   * @param {number} [current.temperature] - 当前温度。
   * @param {number} [current.percent] - 时间进度百分比：0~1
   * @param {number|string} [current.text] - 写在进度条上的循环数或 Melt 的温度
   */
  drawCurrent(current) {
    this.dataCurrent = Utils.valueOf(current, {step: 0, temperature: 0, percent: 0, text: 0});
  }

  /**
   * 绘制小相机
   *
   * @param {number[]} steps - 需要绘制小相机的 steps，0-based
   */
  drawCamera(steps) {
    if (!Array.isArray(steps)) {
      return;
    }
    this.drawCameraMode = true;
    steps.forEach(num => typeof num === "number" && this.dataCamera.add(num));
  }

  /**
   * 绘制每个 step 上的温度和时间
   *
   * @param {{time:string,temperature:string}[]} timeTemperatureList - 温度、时间
   */
  drawTimeTemperatures(timeTemperatureList) {
    if (!Array.isArray(timeTemperatureList)) {
      return;
    }
    this.drawTimeTemperatureMode = true;
    timeTemperatureList.forEach(
      temperature => this.dataTimeTemperatureList[this.dataTimeTemperatureList.length] = temperature);
  }

  /**
   * 绘制左侧温度数值
   *
   * @param {number[]} temperatures - 左侧温度数值
   */
  drawTemperatures(temperatures) {
    if (!Array.isArray(temperatures)) {
      return;
    }
    this.drawTemperatureMode = true;
    temperatures.forEach(temperature => this.dataTemperaturesList[this.dataTemperaturesList.length] = temperature);
  }

  /**
   * 绘制每个 stage 的标题
   *
   * @param {string[]} titles - 每个 stage 的标题
   */
  drawTitle(titles) {
    if (!Array.isArray(titles)) {
      return;
    }
    this.drawTitleMode = true;
    titles.forEach(title => this.dataTitles[this.dataTitles.length] = title);
  }

  /**
   * 绘制每个 stage 间的分隔线
   *
   * @param {number[]} stages - 每个 stage 的 step 数
   * @see setStages
   */
  drawStageSeparationLine(stages) {
    if (!Array.isArray(stages)) {
      return;
    }
    this.drawStageSeparationLineMode = true;
    this.dataStages = stages.filter(stage => typeof stage === "number");
  }

  /**
   * 设置每个 stage 的 step 数，1-based
   *
   * @param {number[]} stages - 每个 stage 的 step 数
   */
  setStages(stages) {
    if (!Array.isArray(stages)) {
      return;
    }
    this.dataStages = stages.filter(stage => typeof stage === "number");
  }

  /**
   * 设置每个 stage 的 cycle 数，1-based
   *
   * @param {number[]} cycles - 每个 stage 的 cycle 数
   */
  setCycles(cycles) {
    if (!Array.isArray(cycles)) {
      return;
    }
    this.dataCycles = cycles.filter(cycle => typeof cycle === "number");
  }

  setWidthLeft(widthLeft) {
    this.widthLeft = Utils.valueOf(widthLeft, 100);
  }

  setWidthRight(widthRight) {
    this.widthRight = Utils.valueOf(widthRight, 100);
  }

  setLineWidthNormal(lineWidthNormal) {
    this.lineWidthNormal = Utils.valueOf(lineWidthNormal, 3);
  }

  setLineWidthBold(lineWidthBold) {
    this.lineWidthBold = Utils.valueOf(lineWidthBold, 8);
  }

  setLineColor(lineColor) {
    this.lineColor = Utils.valueOf(lineColor, "#000000");
  }

  setDrawHorizontalMode(drawHorizontalMode) {
    this.drawHorizontalMode = Utils.valueOf(drawHorizontalMode, ProtocolCanvas.HorizontalMode.FIXED);
  }

  setDrawBackgroundMode(drawBackgroundMode) {
    this.drawBackgroundMode = Utils.valueOf(drawBackgroundMode, true);
  }

  setBackground(background) {
    this.background = Utils.valueOf(background, "#EEEEEE");
  }

  setDrawTemperatureMode(drawTemperatureMode) {
    this.drawTemperatureMode = Utils.valueOf(drawTemperatureMode, false);
  }

  setTemperaturePaddingRight(temperaturePaddingRight) {
    this.temperaturePaddingRight = Utils.valueOf(temperaturePaddingRight, 5);
  }

  setTemperatureBold(temperatureBold) {
    this.temperatureBold = Utils.valueOf(temperatureBold, false);
  }

  setTemperatureSize(temperatureSize) {
    this.temperatureSize = Utils.valueOf(temperatureSize, "21px");
  }

  setTemperatureFamily(temperatureFamily) {
    this.temperatureFamily = Utils.valueOf(temperatureFamily, "Arial");
  }

  setTemperatureColor(temperatureColor) {
    this.temperatureColor = Utils.valueOf(temperatureColor, "#000000");
  }

  setDrawTitleMode(drawTitleMode) {
    this.drawTitleMode = Utils.valueOf(drawTitleMode, false);
  }

  setTitlePaddingTop(titlePaddingTop) {
    this.titlePaddingTop = Utils.valueOf(titlePaddingTop, 10);
  }

  setTitleBold(titleBold) {
    this.titleBold = Utils.valueOf(titleBold, true);
  }

  setTitleSize(titleSize) {
    this.titleSize = Utils.valueOf(titleSize, "21px");
  }

  setTitleFamily(titleFamily) {
    this.titleFamily = Utils.valueOf(titleFamily, "Arial");
  }

  setTitleColor(titleColor) {
    this.titleColor = Utils.valueOf(titleColor, "#5599FF");
  }

  setDrawStageSeparationLineMode(drawStageSeparationLineMode) {
    this.drawStageSeparationLineMode = Utils.valueOf(drawStageSeparationLineMode, false);
  }

  setStageSeparationLineColor(stageSeparationLineColor) {
    this.stageSeparationLineColor = Utils.valueOf(stageSeparationLineColor, "#5599FF");
  }

  setStageSeparationLineWidth(stageSeparationLineWidth) {
    this.stageSeparationLineWidth = Utils.valueOf(stageSeparationLineWidth, 4);
  }

  setDrawStepSeparationLineMode(drawStepSeparationLineMode) {
    this.drawStepSeparationLineMode = Utils.valueOf(drawStepSeparationLineMode, false);
  }

  setStepSeparationLineColor(stepSeparationLineColor) {
    this.stepSeparationLineColor = Utils.valueOf(stepSeparationLineColor, "#5599FF");
  }

  setStepSeparationLineWidth(stepSeparationLineWidth) {
    this.stepSeparationLineWidth = Utils.valueOf(stepSeparationLineWidth, 2);
  }

  setDrawRealtime(drawRealtime) {
    this.drawRealtime = Utils.valueOf(drawRealtime, false);
  }

  setLineColorRealtime(lineColorRealtime) {
    this.lineColorRealtime = Utils.valueOf(lineColorRealtime, "#FF0000");
  }

  setProgressBarPaddingTop(progressBarPaddingTop) {
    this.progressBarPaddingTop = Utils.valueOf(progressBarPaddingTop, 45);
  }

  setProgressBarPaddingHorizontal(progressBarPaddingHorizontal) {
    this.progressBarPaddingHorizontal = Utils.valueOf(progressBarPaddingHorizontal, 20);
  }

  setProgressBarBorderColor(progressBarBorderColor) {
    this.progressBarBorderColor = Utils.valueOf(progressBarBorderColor, "#000000");
  }

  setProgressBarColor(progressBarColor) {
    this.progressBarColor = Utils.valueOf(progressBarColor, "#5599FF");
  }

  setProgressBarCycleColor(progressBarCycleColor) {
    this.progressBarCycleColor = Utils.valueOf(progressBarCycleColor, "#5599FF");
  }

  setProgressBarHeight(progressBarHeight) {
    this.progressBarHeight = Utils.valueOf(progressBarHeight, 16);
  }

  setCameraColor(cameraColor) {
    this.cameraColor = Utils.valueOf(cameraColor, "#00FF00");
  }

  setCameraCenterColor(cameraCenterColor) {
    this.cameraCenterColor = Utils.valueOf(cameraCenterColor, "#FFFFFF");
  }

  setCameraSize(cameraSize) {
    this.cameraSize = Utils.valueOf(cameraSize, 1);
  }

  setCameraPadding(cameraPadding) {
    this.cameraPadding = Utils.valueOf(cameraPadding, 20);
  }

  setDrawTempTimeMode(drawTempTimeMode) {
    this.drawTimeTemperatureMode = Utils.valueOf(drawTempTimeMode, false);
  }

  setTimeTemperatureBold(timeTemperatureBold) {
    this.timeTemperatureBold = Utils.valueOf(timeTemperatureBold, false);
  }

  setTimeTemperatureSize(timeTemperatureSize) {
    this.timeTemperatureSize = Utils.valueOf(timeTemperatureSize, "18px");
  }

  setTimeTemperatureFamily(timeTemperatureFamily) {
    this.timeTemperatureFamily = Utils.valueOf(timeTemperatureFamily, "Arial");
  }

  setTimeTemperatureColor(timeTemperatureColor) {
    this.timeTemperatureColor = Utils.valueOf(timeTemperatureColor, "#000000");
  }

  setTimeTemperaturePaddingVertical(timeTemperaturePaddingVertical) {
    this.timeTemperaturePaddingVertical = Utils.valueOf(timeTemperaturePaddingVertical, 10);
  }
}

class __ProtocolCanvas {
  /**
   * 绘制 protocol canvas 固定数据
   *
   * @param {ProtocolCanvas} pc
   */
  static draw(pc) {
    let {widthLeft, widthRight} = this._getWidth(pc);
    if (widthLeft === undefined || widthRight === undefined) {
      return;
    }

    let arr = [{
      x: pc.left,
      y: pc.bottom,
      lineWidth: 0,
      lineColor: pc.lineColor
    }];
    pc.data.forEach(num => {
      let obj = {
        x: arr[arr.length - 1].x,
        y: pc.getY(num),
        lineWidth: arr[arr.length - 1].lineWidth,
        lineColor: pc.lineColor
      };
      obj.x += widthLeft;
      obj.lineWidth = obj.y === arr[arr.length - 1].y ? pc.lineWidthBold : pc.lineWidthNormal;
      obj.lineColor = pc.lineColor;
      arr[arr.length] = obj;

      arr[arr.length] = {
        x: obj.x + widthRight,
        y: obj.y,
        lineWidth: pc.lineWidthBold,
        lineColor: pc.lineColor
      };
    });

    this._arrDraw(pc, arr);
  }

  /**
   * 绘制 protocol canvas 实时数据
   *
   * @param {ProtocolCanvas} pc
   */
  static drawCurrent(pc) {
    let {widthLeft, widthRight, widthStep} = this._getWidth(pc);
    if (widthLeft === undefined || widthRight === undefined) {
      return;
    }

    let arr = [{
        x: pc.left,
        y: pc.bottom,
        lineWidth: 0,
        lineColor: pc.lineColorRealtime
      }],
      length = Math.min(pc.data.length, pc.dataCurrent.step),
      yCur = pc.getY(pc.dataCurrent.temperature);
    for (let i = 0; i < length; i++) {
      let obj = {
          x: arr[arr.length - 1].x,
          y: pc.getY(pc.data[i]),
          lineWidth: arr[arr.length - 1].lineWidth,
          lineColor: pc.lineColorRealtime
        },
        flagTwoPart = true;
      if (i !== pc.dataCurrent.step - 1) {
        obj.x += widthLeft;
      } else if (obj.y === arr[arr.length - 1].y) {
        obj.x += widthStep * pc.dataCurrent.percent;
        flagTwoPart = false;
      } else {
        let percent = (yCur - arr[arr.length - 1].y) / (obj.y - arr[arr.length - 1].y);
        if (percent > 1) {
          obj.x += widthLeft;
          obj.y = obj.y - arr[arr.length - 1].y + arr[arr.length - 1].y;
        } else {
          obj.x += widthLeft * (yCur - arr[arr.length - 1].y) / (obj.y - arr[arr.length - 1].y);
          obj.y = yCur;
        }
      }
      obj.lineWidth = obj.y === arr[arr.length - 1].y ? pc.lineWidthBold : pc.lineWidthNormal;
      obj.lineColor = pc.lineColorRealtime;
      arr[arr.length] = obj;

      if (i !== pc.dataCurrent.step - 1) {
        arr[arr.length] = {
          x: obj.x + widthRight,
          y: obj.y,
          lineWidth: pc.lineWidthBold,
          lineColor: pc.lineColorRealtime
        };
      } else if (flagTwoPart && yCur === pc.getY(pc.data[i])) {
        arr[arr.length] = {
          x: obj.x + widthRight * pc.dataCurrent.percent,
          y: obj.y,
          lineWidth: pc.lineWidthBold,
          lineColor: pc.lineColorRealtime
        };
      }
    }

    this._arrDraw(pc, arr);
  }

  /**
   * 根据传入数组，调用原生方法，绘制 protocol canvas
   *
   * @param {ProtocolCanvas} pc
   * @param {Array} arr - 包含具体像素点、线宽、线色信息的数组
   * @private
   */
  static _arrDraw(pc, arr) {
    arr.forEach((item, index, array) => {
      if (index === 0) {
        return;
      }
      pc.context.beginPath();
      pc.context.lineWidth = item.lineWidth;
      pc.context.strokeStyle = item.lineColor;
      pc.context.moveTo(array[index - 1].x, array[index - 1].y);
      pc.context.lineTo(item.x, item.y);
      pc.context.stroke();
    });
  }

  /**
   * 绘制每个 stage 的进度条
   *
   * @param {ProtocolCanvas} pc
   */
  static drawProgressBar(pc) {
    let {widthStep} = this._getWidth(pc);
    if (Number.isNaN(widthStep)) {
      return;
    }
    let step = 0,
      radius = pc.progressBarHeight * 0.5,
      x = pc.left + pc.progressBarPaddingHorizontal, y = pc.progressBarPaddingTop + radius;
    pc.dataStages.forEach((stage, index) => {
      let widthStage = stage * widthStep,
        widthProgressBar = widthStage - pc.progressBarPaddingHorizontal * 2;
      // 进度条外框: 两个 arc 自动连和 closePath 封闭成一个空心进度条
      pc.context.beginPath();
      pc.context.lineWidth = 1;
      pc.context.strokeStyle = pc.progressBarBorderColor;
      pc.context.arc(x, y, radius, 0.5 * Math.PI, 1.5 * Math.PI);
      pc.context.arc(x + widthProgressBar, y, radius, 1.5 * Math.PI, 0.5 * Math.PI);
      pc.context.closePath();
      pc.context.stroke();
      // 进度条内容
      pc.context.beginPath();
      pc.context.lineWidth = pc.progressBarHeight - 2;
      pc.context.strokeStyle = pc.progressBarColor;
      pc.context.lineCap = "round";
      pc.context.moveTo(x, y);
      let drawLength;
      if (pc.dataCurrent.step > step + stage) {
        drawLength = widthProgressBar;
      } else if (pc.dataCurrent.step !== 0 && pc.dataCurrent.step > step) {
        if (pc.dataCycles[index] === 1) {
          if (stage === 1) {
            drawLength = widthProgressBar * pc.dataCurrent.percent;
          } else {
            drawLength = widthProgressBar * (pc.dataCurrent.step - step) / stage;
          }
        } else {
          switch (typeof pc.dataCurrent.text) {
            case "number":
              drawLength = widthProgressBar * pc.dataCurrent.text / pc.dataCycles[index];
              break;
            case "string":
              drawLength = widthProgressBar * pc.dataCurrent.percent;
              break;
          }
        }
      }
      pc.context.lineTo(x + drawLength, y);
      pc.context.stroke();
      pc.context.lineCap = "butt";
      // 循环数、摄氏度
      pc.context.fillStyle = pc.progressBarCycleColor;
      pc.context.textAlign = "right";
      pc.context.textBaseline = "middle";
      pc.context.font = `normal ${pc.progressBarHeight - 2}px Arial`;
      if (pc.dataCurrent.step > step + stage) {
        pc.context.fillText(pc.dataCycles[index], x + drawLength, y + 1);
      } else if (pc.dataCurrent.step !== 0 && pc.dataCurrent.step > step) {
        if (drawLength > pc.context.measureText(pc.dataCurrent.text).width + 10) {
          pc.context.fillText(pc.dataCurrent.text, x + drawLength - 5, y + 1);
        } else {
          pc.context.fillStyle = pc.progressBarColor;
          pc.context.textAlign = "left";
          pc.context.fillText(pc.dataCurrent.text, x + drawLength + pc.progressBarHeight * 0.5 + 5, y + 1);
        }
      }

      x += widthStage;
      step += stage;
    });
  }

  /**
   * 画照相机图标
   *
   * @param {ProtocolCanvas} pc
   */
  static drawCamera(pc) {
    let {widthStep} = this._getWidth(pc);
    if (Number.isNaN(widthStep)) {
      return;
    }
    pc.dataCamera.forEach(step => {
      if (pc.data[step] === undefined) {
        return;
      }
      let y = pc.getY(pc.data[step]);
      this._drawCamera(pc, pc.left + widthStep * (step + 1) - pc.cameraSize * 16 - pc.cameraPadding ,
        pc.data[step] > 95 ? y + pc.cameraSize * 15 : y - pc.cameraSize * 15, pc.cameraSize);
    });
  }

  /**
   * 画照相机图标核心方法
   *
   * @param {ProtocolCanvas} pc
   * @param {Number} x - 中心点 x 值
   * @param {Number} y - 中心点 y 值
   * @param {Number} size - 倍数
   * @private
   */
  static _drawCamera(pc, x, y, size) {
    let context = pc.context, halfWidth = 8 * size, halfHeight = 8 * size;
    context.strokeStyle = pc.cameraColor;
    context.fillStyle = pc.cameraColor;
    context.lineWidth = 1;
    // 画边框
    context.beginPath();
    context.moveTo(x - halfWidth, y - halfHeight);
    context.lineTo(x + halfWidth, y - halfHeight);
    context.arc(x + halfWidth, y, halfHeight, -0.5 * Math.PI, 0.5 * Math.PI);
    context.lineTo(x - halfWidth, y + halfHeight);
    context.arc(x - halfWidth, y, halfHeight, 0.5 * Math.PI, -0.5 * Math.PI);
    context.stroke();
    // 画主体
    context.beginPath();
    context.moveTo(x - halfWidth * 1.2, y - halfHeight * 0.5);
    context.lineTo(x - halfWidth * 0.75, y - halfHeight * 0.5);
    context.lineTo(x - halfWidth * 0.75, y - halfHeight * 0.75);
    context.lineTo(x + halfWidth * 0.75, y - halfHeight * 0.75);
    context.lineTo(x + halfWidth * 0.75, y - halfHeight * 0.5);
    context.lineTo(x + halfWidth * 1.2, y - halfHeight * 0.5);
    context.lineTo(x + halfWidth * 1.2, y + halfHeight * 0.75);
    context.lineTo(x - halfWidth * 1.2, y + halfHeight * 0.75);
    context.closePath();
    context.fill();
    // 画中心圆 - 底
    context.beginPath();
    context.arc(x, y, halfHeight * 0.5, 0, 2 * Math.PI);
    context.fillStyle = pc.cameraCenterColor;
    context.fill();
    // 画中心圆 - 顶
    context.beginPath();
    context.arc(x, y, halfHeight * 0.3, 0, 2 * Math.PI);
    context.fillStyle = pc.cameraColor;
    context.fill();
  }

  /**
   * 画时间和温度
   *
   * @param {ProtocolCanvas} pc
   */
  static drawTimeTemperatures(pc) {
    let {widthLeft, widthRight} = this._getWidth(pc);
    if (widthLeft === undefined || widthRight === undefined) {
      return;
    }

    let x = pc.left;
    pc.context.textAlign = "center";
    pc.context.fillStyle = pc.timeTemperatureColor;
    pc.context.font =
      `${pc.timeTemperatureBold ? "bold" : "normal"} ${pc.timeTemperatureSize} ${pc.timeTemperatureFamily}`;
    pc.data.some((num, index) => {
      let timeTemperature = pc.dataTimeTemperatureList[index];
      if (timeTemperature === undefined) {
        return true;
      }
      let y = pc.getY(num);
      x += (widthLeft + widthRight * 0.4);
      pc.context.textBaseline = "bottom";
      pc.context.fillText(timeTemperature.temperature, x, y - pc.timeTemperaturePaddingVertical);
      pc.context.textBaseline = "top";
      pc.context.fillText(timeTemperature.time, x, y + pc.timeTemperaturePaddingVertical);
      x += widthRight * 0.6;
      return false;
    });
  }

  /**
   * 绘制温度数值
   *
   * @param {ProtocolCanvas} pc
   */
  static drawTemperature(pc) {
    pc.context.textAlign = "right";
    pc.context.textBaseline = "middle";
    pc.context.fillStyle = pc.temperatureColor;
    pc.context.font = `${pc.temperatureBold ? "bold" : "normal"} ${pc.temperatureSize} ${pc.temperatureFamily}`;
    pc.dataTemperaturesList.forEach(temperature => pc.context.fillText(temperature,
      pc.left - pc.temperaturePaddingRight, pc.getY(temperature)));
  }

  /**
   * 绘制每个 stage 的标题
   *
   * @param {ProtocolCanvas} pc
   */
  static drawTitle(pc) {
    let {widthStep} = this._getWidth(pc);
    if (Number.isNaN(widthStep)) {
      return;
    }
    pc.context.textAlign = "center";
    pc.context.textBaseline = "top";
    pc.context.fillStyle = pc.titleColor;
    pc.context.font = `${pc.titleBold ? "bold" : "normal"} ${pc.titleSize} ${pc.titleFamily}`;
    let x = pc.left;
    pc.dataTitles.forEach((title, index) => {
      let widthStage = pc.dataStages[index] * widthStep;
      pc.context.fillText(title, x + widthStage * 0.5, pc.titlePaddingTop);
      x += widthStage;
    });
  }

  /**
   * 绘制 stage 间的分隔线
   *
   * @param {ProtocolCanvas} pc
   */
  static drawStageSeparationLine(pc) {
    let {widthStep} = this._getWidth(pc);
    if (Number.isNaN(widthStep)) {
      return;
    }
    pc.context.strokeStyle = pc.stageSeparationLineColor;
    pc.context.lineWidth = pc.stageSeparationLineWidth;
    let x = pc.left;
    pc.context.beginPath();
    pc.context.moveTo(x, 0);
    pc.context.lineTo(x, pc.canvas.height);
    pc.context.stroke();
    pc.dataStages.forEach(stage => {
      x += stage * widthStep;
      pc.context.beginPath();
      pc.context.moveTo(x, 0);
      pc.context.lineTo(x, pc.canvas.height);
      pc.context.stroke();
    });
  }

  /**
   * 绘制 step 间的分隔线
   *
   * @param {ProtocolCanvas} pc
   */
  static drawStepSeparationLine(pc) {
    let {widthStep} = this._getWidth(pc);
    if (Number.isNaN(widthStep)) {
      return;
    }
    pc.context.strokeStyle = pc.stepSeparationLineColor;
    pc.context.lineWidth = pc.stepSeparationLineWidth;
    for (let i = 0; i < pc.data.length + 1; i++) {
      pc.context.beginPath();
      pc.context.moveTo(pc.left + i * widthStep, pc.top);
      pc.context.lineTo(pc.left + i * widthStep, pc.bottom);
      pc.context.stroke();
    }
  }

  /**
   * 获取每个 step 的左右两部分宽度
   *
   * @param {ProtocolCanvas} pc
   * @private
   */
  static _getWidth(pc) {
    let widthLeft, widthRight;
    if (pc.drawHorizontalMode === ProtocolCanvas.HorizontalMode.FIXED) {
      widthLeft = pc.widthLeft;
      widthRight = pc.widthRight;
    } else if (pc.drawHorizontalMode === ProtocolCanvas.HorizontalMode.AUTO) {
      let widthPerStep = pc.contentWidth / pc.data.length, widthSetting = pc.widthLeft + pc.widthRight;
      widthLeft = widthPerStep * pc.widthLeft / widthSetting;
      widthRight = widthPerStep * pc.widthRight / widthSetting;
    }
    return {widthLeft, widthRight, widthStep: widthLeft + widthRight};
  }
}

/**
 * 绘制数据的 canvas 插件的父类
 *
 * @class
 */
class DataCanvas extends MyCanvas {
  constructor(id, initParam = {}) {
    super(id, initParam);

    this.axisTitleTop = Utils.valueOf(initParam.axisTitleTop, "");
    this.axisTitleRight = Utils.valueOf(initParam.axisTitleRight, "");
    this.axisTitleBottom = Utils.valueOf(initParam.axisTitleBottom, "");
    this.axisTitleLeft = Utils.valueOf(initParam.axisTitleLeft, "");
    this.axisTitleTopPadding = Utils.valueOf(initParam.axisTitleTopPadding, 0);
    this.axisTitleRightPadding = Utils.valueOf(initParam.axisTitleRightPadding, 0);
    this.axisTitleBottomPadding = Utils.valueOf(initParam.axisTitleBottomPadding, 0);
    this.axisTitleLeftPadding = Utils.valueOf(initParam.axisTitleLeftPadding, 0);
    this.axisTitleTopBold = Utils.valueOf(initParam.axisTitleTopBold, false);
    this.axisTitleRightBold = Utils.valueOf(initParam.axisTitleRightBold, false);
    this.axisTitleBottomBold = Utils.valueOf(initParam.axisTitleBottomBold, false);
    this.axisTitleLeftBold = Utils.valueOf(initParam.axisTitleLeftBold, false);
    this.axisTitleTopSize = Utils.valueOf(initParam.axisTitleTopSize, "21px");
    this.axisTitleRightSize = Utils.valueOf(initParam.axisTitleRightSize, "21px");
    this.axisTitleBottomSize = Utils.valueOf(initParam.axisTitleBottomSize, "21px");
    this.axisTitleLeftSize = Utils.valueOf(initParam.axisTitleLeftSize, "21px");
    this.axisTitleTopFamily = Utils.valueOf(initParam.axisTitleTopFamily, "Arial");
    this.axisTitleRightFamily = Utils.valueOf(initParam.axisTitleRightFamily, "Arial");
    this.axisTitleBottomFamily = Utils.valueOf(initParam.axisTitleBottomFamily, "Arial");
    this.axisTitleLeftFamily = Utils.valueOf(initParam.axisTitleLeftFamily, "Arial");
    this.axisTitleTopColor = Utils.valueOf(initParam.axisTitleTopColor, "#000000");
    this.axisTitleRightColor = Utils.valueOf(initParam.axisTitleRightColor, "#000000");
    this.axisTitleBottomColor = Utils.valueOf(initParam.axisTitleBottomColor, "#000000");
    this.axisTitleLeftColor = Utils.valueOf(initParam.axisTitleLeftColor, "#000000");
    this.dataPaddingTop = Utils.valueOf(initParam.dataPaddingTop, 20);
    this.dataPaddingBottom = Utils.valueOf(initParam.dataPaddingBottom, 20);
    this.dataPaddingLeft = Utils.valueOf(initParam.dataPaddingLeft, 20);
    this.dataPaddingRight = Utils.valueOf(initParam.dataPaddingRight, 20);
    this.drawAxisMode = Utils.valueOf(initParam.drawAxisMode, true);
    this.axisType = Utils.valueOf(initParam.axisType, AxisType.SQUARE);
    this.axisStyle = Utils.valueOf(initParam.axisStyle, BorderStyle.SOLID);
    this.axisColor = Utils.valueOf(initParam.axisColor, "#000000");
    this.axisLineWidth = Utils.valueOf(initParam.axisLineWidth, 2);
    this.drawAxisTitleMode = Utils.valueOf(initParam.drawAxisTitleMode, true);
    this.drawAxisNumberMode = Utils.valueOf(initParam.drawAxisNumberMode, true);
    this.drawLegendMode = Utils.valueOf(initParam.drawLegendMode, true);
    this.legendTextColor = Utils.valueOf(initParam.legendTextColor, "#000000");

    this.__mouseWheel = e => {
      if (e.deltaY > 0) {
        this.scale = Math.max(1, this.scale - 0.1);
      } else if (e.deltaY < 0) {
        this.scale = Math.min(10, this.scale + 0.1);
      }
      this.resize();
    };
    this.canvas.addEventListener("wheel", this.__mouseWheel);
  }

  resize(widthScale = 1, heightScale = 1) {
    super.resize(widthScale, heightScale);
    this.dataLeft = this.left + this.dataPaddingLeft;
    this.dataRight = this.right - this.dataPaddingRight;
    this.dataTop = this.top + this.dataPaddingTop;
    this.dataBottom = this.bottom - this.dataPaddingBottom;

    this.dataContentHeight = this.dataBottom - this.dataTop;
    this.dataContentWidth = this.dataRight - this.dataLeft;

    this.coeY = this.dataContentHeight / (this.maxY - this.minY);
    this.coeX = this.dataContentWidth / (this.maxX - this.minX);
  }

  /** @inheritDoc */
  destroyCanvas() {
    this.canvas.removeEventListener("wheel", this.__mouseWheel);
    super.destroyCanvas();
  }

  /**
   * 坐标轴
   */
  __drawAxis() {
    let context = this.context,
      halfLineWidth = this.axisLineWidth * 0.5,
      twoLineWidth = this.axisLineWidth * 2,
      threeLineWidth = this.axisLineWidth * 3,
      fiveLineWidth = this.axisLineWidth * 5;
    context.fillStyle = this.axisColor;
    context.strokeStyle = this.axisColor;
    context.lineWidth = this.axisLineWidth;
    switch (this.axisType) {
      case AxisType.SQUARE:
        switch (this.axisStyle) {
          case BorderStyle.SOLID:
            context.beginPath();
            context.moveTo(this.left, this.top);
            context.lineTo(this.right, this.top);
            context.lineTo(this.right, this.bottom);
            context.lineTo(this.left, this.bottom);
            context.closePath();
            context.stroke();
            break;
          case BorderStyle.DASHED:
            context.beginPath();
            for (let i = 0; i < this.contentHeight - twoLineWidth; i += fiveLineWidth) {
              // 左
              context.moveTo(this.left, this.bottom - i);
              context.lineTo(this.left, this.bottom - i - threeLineWidth);
              // 右
              context.moveTo(this.left + this.contentWidth, this.bottom - i);
              context.lineTo(this.left + this.contentWidth, this.bottom - i - threeLineWidth);
            }
            for (let i = 0; i < this.contentWidth - twoLineWidth; i += fiveLineWidth) {
              // 上
              context.moveTo(this.left + i, this.top);
              context.lineTo(this.left + i + threeLineWidth, this.top);
              // 下
              context.moveTo(this.left + i, this.bottom);
              context.lineTo(this.left + i + threeLineWidth, this.bottom);
            }
            context.stroke();
            break;
          case BorderStyle.DOTTED:
            for (let i = 0; i < this.contentHeight; i += fiveLineWidth) {
              // 左
              context.beginPath();
              context.arc(this.left, this.bottom - i, halfLineWidth, 0, 2 * Math.PI);
              context.stroke();
              // 右
              context.beginPath();
              context.arc(this.right, this.bottom - i, halfLineWidth, 0, 2 * Math.PI);
              context.stroke();
            }
            for (let i = 0; i < this.contentWidth; i += fiveLineWidth) {
              // 上
              context.beginPath();
              context.arc(this.left + i, this.top, halfLineWidth, 0, 2 * Math.PI);
              context.stroke();
              // 下
              context.beginPath();
              context.arc(this.left + i, this.bottom, halfLineWidth, 0, 2 * Math.PI);
              context.stroke();
            }
            break;
        }
        break;
      case AxisType.NORMAL:
        switch (this.axisStyle) {
          case BorderStyle.SOLID:
            context.beginPath();
            context.moveTo(this.left - threeLineWidth, this.top + threeLineWidth);
            context.lineTo(this.left, this.top);
            context.lineTo(this.left + threeLineWidth, this.top + threeLineWidth);

            context.moveTo(this.left, this.top);
            context.lineTo(this.left, this.bottom);
            context.lineTo(this.right, this.bottom);

            context.moveTo(this.right - threeLineWidth, this.bottom - threeLineWidth);
            context.lineTo(this.right, this.bottom);
            context.lineTo(this.right - threeLineWidth, this.bottom + threeLineWidth);
            context.stroke();
            break;
          case BorderStyle.DOTTED:
            context.beginPath();
            context.moveTo(this.left - threeLineWidth, this.top + threeLineWidth);
            context.lineTo(this.left, this.top);
            context.lineTo(this.left + threeLineWidth, this.top + threeLineWidth);

            context.moveTo(this.right - threeLineWidth, this.bottom - threeLineWidth);
            context.lineTo(this.right, this.bottom);
            context.lineTo(this.right - threeLineWidth, this.bottom + threeLineWidth);
            context.stroke();
            for (let i = 0; i < this.contentWidth; i += fiveLineWidth) {
              context.beginPath();
              context.arc(this.left + i, this.bottom, halfLineWidth, 0, 2 * Math.PI);
              context.stroke();
            }
            for (let i = 0; i < this.contentHeight; i += fiveLineWidth) {
              context.beginPath();
              context.arc(this.left, this.bottom - i, halfLineWidth, 0, 2 * Math.PI);
              context.stroke();
            }
            break;
          case BorderStyle.DASHED:
            context.beginPath();
            context.moveTo(this.left - threeLineWidth, this.top + threeLineWidth);
            context.lineTo(this.left, this.top);
            context.lineTo(this.left + threeLineWidth, this.top + threeLineWidth);

            context.moveTo(this.right - threeLineWidth, this.bottom - threeLineWidth);
            context.lineTo(this.right, this.bottom);
            context.lineTo(this.right - threeLineWidth, this.bottom + threeLineWidth);
            for (let i = 0; i < this.contentWidth - twoLineWidth; i += fiveLineWidth) {
              context.moveTo(this.left + i, this.bottom);
              context.lineTo(this.left + i + threeLineWidth, this.bottom);
            }
            for (let i = 0; i < this.contentHeight - twoLineWidth; i += fiveLineWidth) {
              context.moveTo(this.left, this.bottom - i);
              context.lineTo(this.left, this.bottom - i - threeLineWidth);
            }
            context.stroke();
            break;
        }
        break;
    }
  }

  __drawAxisTitle() {
    const ctx = this.context;
    ctx.textAlign = "center";
    // 上标题
    if (this.axisTitleTop !== "") {
      ctx.textBaseline = "bottom";
      ctx.font = `${this.axisTitleTopBold ? "bold" : "normal"} ${this.axisTitleTopSize} ${this.axisTitleTopFamily}`;
      ctx.fillStyle = this.axisTitleTopColor;
      ctx.fillText(this.axisTitleTop, (this.left + this.right) * 0.5, this.top - (5 + this.axisTitleTopPadding));
    }
    // 下标题
    if (this.axisTitleBottom !== "") {
      ctx.textBaseline = "top";
      ctx.font =
        `${this.axisTitleBottomBold ? "bold" : "normal"} ${this.axisTitleBottomSize} ${this.axisTitleBottomFamily}`;
      ctx.fillStyle = this.axisTitleBottomColor;
      ctx.fillText(this.axisTitleBottom, (this.left + this.right) * 0.5,
        this.bottom + (5 + this.axisTitleBottomPadding));
    }
    // 右标题
    if (this.axisTitleRight !== "") {
      ctx.textBaseline = "bottom";
      ctx.font =
        `${this.axisTitleRightBold ? "bold" : "normal"} ${this.axisTitleRightSize} ${this.axisTitleRightFamily}`;
      ctx.fillStyle = this.axisTitleRightColor;
      ctx.translate(this.width, this.height * 0.5);
      ctx.rotate(Math.PI * 0.5);
      ctx.fillText(this.axisTitleRight, 0, this.paddingRight - (5 + this.axisTitleRightPadding));
      ctx.rotate(-Math.PI * 0.5);
      ctx.translate(-this.width, -this.height * 0.5);
    }
    // 左标题
    if (this.axisTitleLeft !== "") {
      ctx.textBaseline = "bottom";
      ctx.font = `${this.axisTitleLeftBold ? "bold" : "normal"} ${this.axisTitleLeftSize} ${this.axisTitleLeftFamily}`;
      ctx.fillStyle = this.axisTitleLeftColor;
      ctx.translate(0, this.height * 0.5);
      ctx.rotate(-Math.PI * 0.5);
      ctx.fillText(this.axisTitleLeft, 0, this.paddingLeft - (5 + this.axisTitleLeftPadding));
      ctx.rotate(Math.PI * 0.5);
      ctx.translate(0, -this.height * 0.5);
    }
  }

  setAxisTitleTop(axisTitleTop) {
    this.axisTitleTop = Utils.valueOf(axisTitleTop, "");
  }

  setAxisTitleRight(axisTitleRight) {
    this.axisTitleRight = Utils.valueOf(axisTitleRight, "");
  }

  setAxisTitleBottom(axisTitleBottom) {
    this.axisTitleBottom = Utils.valueOf(axisTitleBottom, "");
  }

  setAxisTitleLeft(axisTitleLeft) {
    this.axisTitleLeft = Utils.valueOf(axisTitleLeft, "");
  }

  setAxisTitleTopPadding(axisTitleTopPadding) {
    this.axisTitleTopPadding = Utils.valueOf(axisTitleTopPadding, 0);
  }

  setAxisTitleRightPadding(axisTitleRightPadding) {
    this.axisTitleRightPadding = Utils.valueOf(axisTitleRightPadding, 0);
  }

  setAxisTitleBottomPadding(axisTitleBottomPadding) {
    this.axisTitleBottomPadding = Utils.valueOf(axisTitleBottomPadding, 0);
  }

  setAxisTitleLeftPadding(axisTitleLeftPadding) {
    this.axisTitleLeftPadding = Utils.valueOf(axisTitleLeftPadding, 0);
  }

  setAxisTitleTopBold(axisTitleTopBold) {
    this.axisTitleTopBold = Utils.valueOf(axisTitleTopBold, false);
  }

  setAxisTitleRightBold(axisTitleRightBold) {
    this.axisTitleRightBold = Utils.valueOf(axisTitleRightBold, false);
  }

  setAxisTitleBottomBold(axisTitleBottomBold) {
    this.axisTitleBottomBold = Utils.valueOf(axisTitleBottomBold, false);
  }

  setAxisTitleLeftBold(axisTitleLeftBold) {
    this.axisTitleLeftBold = Utils.valueOf(axisTitleLeftBold, false);
  }

  setAxisTitleTopSize(axisTitleTopSize) {
    this.axisTitleTopSize = Utils.valueOf(axisTitleTopSize, "21px");
  }

  setAxisTitleRightSize(axisTitleRightSize) {
    this.axisTitleRightSize = Utils.valueOf(axisTitleRightSize, "21px");
  }

  setAxisTitleBottomSize(axisTitleBottomSize) {
    this.axisTitleBottomSize = Utils.valueOf(axisTitleBottomSize, "21px");
  }

  setAxisTitleLeftSize(axisTitleLeftSize) {
    this.axisTitleLeftSize = Utils.valueOf(axisTitleLeftSize, "21px");
  }

  setAxisTitleTopFamily(axisTitleTopFamily) {
    this.axisTitleTopFamily = Utils.valueOf(axisTitleTopFamily, "Arial");
  }

  setAxisTitleRightFamily(axisTitleRightFamily) {
    this.axisTitleRightFamily = Utils.valueOf(axisTitleRightFamily, "Arial");
  }

  setAxisTitleBottomFamily(axisTitleBottomFamily) {
    this.axisTitleBottomFamily = Utils.valueOf(axisTitleBottomFamily, "Arial");
  }

  setAxisTitleLeftFamily(axisTitleLeftFamily) {
    this.axisTitleLeftFamily = Utils.valueOf(axisTitleLeftFamily, "Arial");
  }

  setAxisTitleTopColor(axisTitleTopColor) {
    this.axisTitleTopColor = Utils.valueOf(axisTitleTopColor, "#000000");
  }

  setAxisTitleRightColor(axisTitleRightColor) {
    this.axisTitleRightColor = Utils.valueOf(axisTitleRightColor, "#000000");
  }

  setAxisTitleBottomColor(axisTitleBottomColor) {
    this.axisTitleBottomColor = Utils.valueOf(axisTitleBottomColor, "#000000");
  }

  setAxisTitleLeftColor(axisTitleLeftColor) {
    this.axisTitleLeftColor = Utils.valueOf(axisTitleLeftColor, "#000000");
  }

  setDataPaddingTop(dataPaddingTop) {
    this.dataPaddingTop = Utils.valueOf(dataPaddingTop, 0);
    this.resize();
  }

  setDataPaddingBottom(dataPaddingBottom) {
    this.dataPaddingBottom = Utils.valueOf(dataPaddingBottom, 0);
    this.resize();
  }

  setDataPaddingLeft(dataPaddingLeft) {
    this.dataPaddingLeft = Utils.valueOf(dataPaddingLeft, 0);
    this.resize();
  }

  setDataPaddingRight(dataPaddingRight) {
    this.dataPaddingRight = Utils.valueOf(dataPaddingRight, 0);
    this.resize();
  }

  setDrawAxisMode(drawAxisMode) {
    this.drawAxisMode = Utils.valueOf(drawAxisMode, true);
  }

  setAxisType(axisType) {
    this.axisType = Utils.valueOf(axisType, AxisType.SQUARE);
  }

  setAxisStyle(axisStyle) {
    this.axisStyle = Utils.valueOf(axisStyle, BorderStyle.SOLID);
  }

  setAxisColor(axisColor) {
    this.axisColor = Utils.valueOf(axisColor, "#000000");
  }

  setAxisLineWidth(axisLineWidth) {
    this.axisLineWidth = Utils.valueOf(axisLineWidth, 2);
  }

  setDrawAxisTitleMode(drawAxisTitleMode) {
    this.drawAxisTitleMode = Utils.valueOf(drawAxisTitleMode, true);
  }

  setDrawAxisNumberMode(drawAxisNumberMode) {
    this.drawAxisNumberMode = Utils.valueOf(drawAxisNumberMode, true);
  }

  setDrawLegendMode(drawLegendMode) {
    this.drawLegendMode = Utils.valueOf(drawLegendMode, true);
  }

  setLegendTextColor(legendTextColor) {
    this.legendTextColor = Utils.valueOf(legendTextColor, "#000000");
  }

  /** @inheritDoc */
  getX(x) {
    return this.dataLeft + (x - this.minX) * this.coeX;
  }

  /** @inheritDoc */
  getY(y) {
    return this.dataBottom - (y - this.minY) * this.coeY;
  }

  /**
   * 根据图上位置反算真实数据
   *
   * @param {number} x
   */
  getRealX(x) {
    return (x - this.dataLeft) / this.coeX + this.minX;
  }

  /**
   * 根据图上位置反算真实数据
   *
   * @param {number} y
   */
  getRealY(y) {
    return (this.dataBottom - y) / this.coeY + this.minY;
  }
}

/**
 * 绘制实时温度的 canvas 插件
 *
 * @class
 */
export class TemperatureCanvas extends DataCanvas {
  constructor(id, initParam = {}) {
    super(id, initParam);

    this.lineWidthSample = Utils.valueOf(initParam.lineWidthSample, 1);
    this.lineColorSample = Utils.valueOf(initParam.lineColorSample, "#66CC44");
    this.lineWidthLid = Utils.valueOf(initParam.lineWidthLid, 1);
    this.lineColorLid = Utils.valueOf(initParam.lineColorLid, "#5599FF");
    this.axisTitleX = Utils.valueOf(initParam.axisTitleX, "s/dot");
    this.axisTitleY = Utils.valueOf(initParam.axisTitleY, "℃");
    this.initMaxX = Utils.valueOf(initParam.initMaxX, 1000);
    this.maxX = this.initMaxX;

    this.scale = 1;
    this.dataLidSample = [];
  }

  /**
   * 初始化组件
   *
   * @param {string} id 父容器的 id
   * @param {Object} [initParam] - 初始化参数
   * @param {boolean} [initParam.drawWholeCanvasBackgroundMode] - 是否绘制整个 canvas 的背景色，默认值为 false。
   * @param {string} [initParam.wholeCanvasBackground] - 整个 canvas 的背景色，默认值为 "#FFFFFF"。
   * @param {number} [initParam.paddingTop] - 绘制区域距 canvas 顶部距离，默认值为 0。
   * @param {number} [initParam.paddingBottom] - 绘制区域距 canvas 底部距离，默认值为 0。
   * @param {number} [initParam.paddingLeft] - 绘制区域距 canvas 左边距离，默认值为 0。
   * @param {number} [initParam.paddingRight] - 绘制区域距 canvas 右边距离，默认值为 0。
   * @param {number} [initParam.maxY] - 设置绘制数据的最大值（纵轴），默认值为 Infinity。
   * @param {number} [initParam.minY] - 设置绘制数据的最小值（纵轴），默认值为 0。
   * @param {number} [initParam.initMaxX] - 设置绘制数据的初始最大值（横轴），默认值为 1000。
   * @param {number} [initParam.minX] - 设置绘制数据的最小值（横轴），默认值为 0。
   * @param {number} [initParam.dataPaddingTop] - 绘制数据距绘制区域顶部距离，默认值为 0。
   * @param {number} [initParam.dataPaddingBottom] - 绘制数据距绘制区域底部距离，默认值为 0。
   * @param {number} [initParam.dataPaddingLeft] - 绘制数据距绘制区域左边距离，默认值为 0。
   * @param {number} [initParam.dataPaddingRight] - 绘制数据距绘制区域右边距离，默认值为 0。
   * @param {boolean} [initParam.drawAxisMode] - 是否绘制坐标轴，默认值为 true。
   * @param {AxisType.NORMAL|AxisType.SQUARE|AxisType.NONE} [initParam.axisType] - 坐标轴类型，默认值为 AxisType.SQUARE。
   * @param {BorderStyle.DOTTED|BorderStyle.DASHED|BorderStyle.SOLID} [initParam.axisStyle] - 坐标轴边框类型，默认值为
   *   BorderStyle.SOLID。
   * @param {string} [initParam.axisColor] - 坐标轴颜色，默认值为 "#000000"。
   * @param {number} [initParam.axisLineWidth] - 坐标轴线宽，默认值为 2。
   * @param {boolean} [initParam.drawAxisTitleMode] - 是否绘制坐标轴上轴的标题，默认值为 true。
   * @param {boolean} [initParam.drawAxisNumberMode] - 是否绘制坐标轴上轴的数值标注，默认值为 true。
   * @param {boolean} [initParam.drawLegendMode] - 是否绘制图例，默认值为 true。
   * @param {String} [initParam.legendTextColor] - 图例颜色，默认值为 "#000000"。
   * @param {number} [initParam.lineWidthSample] - sample 数据线宽，默认值为 1。
   * @param {string} [initParam.lineColorSample] - sample 数据颜色，默认值为 "#66CC44"。
   * @param {number} [initParam.lineWidthLid] - lid 数据线宽，默认值为 1。
   * @param {string} [initParam.lineColorLid] - lid 数据颜色，默认值为 "#5599FF"。
   * @param {string} [initParam.axisTitleX] - 横坐标轴的标题，默认值为 "s/dot"。
   * @param {string} [initParam.axisTitleY] - 纵坐标轴的标题，默认值为 "℃"。
   * @returns {TemperatureCanvas}
   */
  static init(id, initParam = {}) {
    let canvas = new TemperatureCanvas(id, initParam);
    canvas.resize();
    canvas.__animation();
    return canvas;
  }

  /**
   * 实现水平缩放
   *
   * @override
   */
  resize() {
    super.resize(this.scale);
  }

  /**
   * @inheritdoc
   */
  refresh() {
    super.refresh();
    __TemperatureCanvas.fixMaxX(this);
    if (this.drawAxisMode) {
      this.__drawAxis();
    }
    if (this.drawLegendMode) {
      this.__drawLegend();
    }
    if (this.drawAxisTitleMode) {
      this.__drawCoordinateAxisTitle();
      this.__drawAxisTitle();
    }
    if (this.drawAxisNumberMode) {
      this.__drawAxisNumber();
    }
    __TemperatureCanvas.draw(this);
  }

  /** @inheritDoc */
  clearData() {
    this.scale = 1;
    this.dataLidSample.length = 0;
  }

  __drawLegend() {
    this.context.textAlign = "start";
    this.context.textBaseline = "middle";
    this.context.font = "14px Arial";
    this.context.fillStyle = this.legendTextColor;

    let left = Math.max(0, this.left - 25), bottom = this.bottom + 3 * this.axisLineWidth + 25;
    this.context.beginPath();
    this.context.moveTo(left, bottom);
    this.context.lineTo(left += 50, bottom);
    this.context.strokeStyle = this.lineColorLid;
    this.context.stroke();
    this.context.fillText("Lid", left += 5, bottom);

    this.context.beginPath();
    this.context.moveTo(left += (this.context.measureText("Lid").width + 20), bottom);
    this.context.lineTo(left += 50, bottom);
    this.context.strokeStyle = this.lineColorSample;
    this.context.stroke();
    this.context.fillText("Sample", left + 5, bottom);
  }

  __drawCoordinateAxisTitle() {
    this.context.textAlign = "right";
    this.context.textBaseline = "top";
    this.context.font = "16px Arial";
    this.context.fillStyle = "#000000";
    // 摄氏度标志
    this.context.fillText(this.axisTitleY, this.left - 3 * this.axisLineWidth - 5, this.top);
    // 时间标志
    this.context.fillText(this.axisTitleX, this.right, this.bottom + 3 * this.axisLineWidth + 5);
  }

  __drawAxisTitle() {
    const ctx = this.context;
    ctx.textAlign = "center";
    // 上标题
    if (this.axisTitleTop !== "") {
      ctx.textBaseline = "bottom";
      ctx.font = `${this.axisTitleTopBold ? "bold" : "normal"} ${this.axisTitleTopSize} ${this.axisTitleTopFamily}`;
      ctx.fillStyle = this.axisTitleTopColor;
      ctx.fillText(this.axisTitleTop, (this.left + this.right) * 0.5, this.top - (5 + this.axisTitleTopPadding));
    }
    // 下标题
    if (this.axisTitleBottom !== "") {
      ctx.textBaseline = "top";
      ctx.font =
        `${this.axisTitleBottomBold ? "bold" : "normal"} ${this.axisTitleBottomSize} ${this.axisTitleBottomFamily}`;
      ctx.fillStyle = this.axisTitleBottomColor;
      ctx.fillText(this.axisTitleBottom, (this.left + this.right) * 0.5,
        this.bottom + (5 + this.axisTitleBottomPadding));
    }
    // 右标题
    if (this.axisTitleRight !== "") {
      ctx.textBaseline = "bottom";
      ctx.font =
        `${this.axisTitleRightBold ? "bold" : "normal"} ${this.axisTitleRightSize} ${this.axisTitleRightFamily}`;
      ctx.fillStyle = this.axisTitleRightColor;
      ctx.translate(this.width, this.height * 0.5);
      ctx.rotate(Math.PI * 0.5);
      ctx.fillText(this.axisTitleRight, 0, this.paddingRight - (5 + this.axisTitleRightPadding));
      ctx.rotate(-Math.PI * 0.5);
      ctx.translate(-this.width, -this.height * 0.5);
    }
    // 左标题
    if (this.axisTitleLeft !== "") {
      ctx.textBaseline = "bottom";
      ctx.font = `${this.axisTitleLeftBold ? "bold" : "normal"} ${this.axisTitleLeftSize} ${this.axisTitleLeftFamily}`;
      ctx.fillStyle = this.axisTitleLeftColor;
      ctx.translate(0, this.height * 0.5);
      ctx.rotate(-Math.PI * 0.5);
      ctx.fillText(this.axisTitleLeft, 0, this.paddingLeft - (5 + this.axisTitleLeftPadding));
      ctx.rotate(Math.PI * 0.5);
      ctx.translate(0, -this.height * 0.5);
    }
  }

  __drawAxisNumber() {
    const ctx = this.context;
    ctx.textAlign = "right";
    ctx.textBaseline = "middle";
    ctx.font = "16px Arial";
    ctx.fillStyle = "#000000";
    const x = this.left - this.axisLineWidth - 5;
    for (let i = Math.ceil(this.minY / 10) * 10; i <= this.maxY; i += 10) {
      ctx.fillText(i + "", x, this.dataBottom - (i - this.minY) * this.coeY);
    }

    ctx.textAlign = "center";
    ctx.textBaseline = "top";
    const xTemp = (this.maxX - this.minX) / 10,
      y = this.dataBottom + this.axisLineWidth + 5;
    for (let i = 0; i < 10; i++) {
      ctx.fillText(this.maxX - xTemp * i + "", this.dataLeft + (this.maxX - xTemp * i) * this.coeX, y);
    }
  }

  /**
   * 绘制 热盖 和 样本 温度
   *
   * @param {object[]} lidAndSampleTemperatures
   * @param {number} lidAndSampleTemperatures[].timestamp - 温度时间戳
   * @param {number} lidAndSampleTemperatures[].lid - 热盖温度
   * @param {number} lidAndSampleTemperatures[].sample - 样本温度
   */
  drawLidAndSampleTemperature(lidAndSampleTemperatures) {
    if (!Array.isArray(lidAndSampleTemperatures)) {
      return;
    }
    lidAndSampleTemperatures.forEach(
      lid => typeof lid === "object" && (this.dataLidSample[this.dataLidSample.length] = lid));
  }

  // 重写方法以实现禁止设置 minX
  setMinX(minX) {
  }
}

class __TemperatureCanvas {
  /** @param {TemperatureCanvas} tc */
  static draw(tc) {
    if (tc.dataLidSample.length < 2) {
      return;
    }

    this.__draw(tc, tc.lineColorLid, tc.lineWidthLid, "lid");
    this.__draw(tc, tc.lineColorSample, tc.lineWidthSample, "sample");
  }

  static __draw(tc, lineColor, lineWidth, prop) {
    tc.context.strokeStyle = lineColor;
    tc.context.lineWidth = lineWidth;
    tc.context.beginPath();
    tc.context.moveTo(tc.left, tc.getY(tc.dataLidSample[0][prop]));
    tc.dataLidSample.forEach(data =>
      tc.context.lineTo(tc.getX((data.timestamp - tc.dataLidSample[0].timestamp) * 0.001), tc.getY(data[prop])));
    tc.context.stroke();
  }

  /** @param {TemperatureCanvas} tc */
  static fixMaxX(tc) {
    if (tc.dataLidSample.length < 2) {
      return;
    }
    let diff = (tc.dataLidSample[tc.dataLidSample.length - 1].timestamp - tc.dataLidSample[0].timestamp) * 0.001;
    if (diff > tc.initMaxX) {
      let newMax = Math.ceil(diff * 0.01) * 100;
      if (tc.maxX !== newMax) {
        tc.maxX = newMax;
        tc.coeX = tc.dataContentWidth / (tc.maxX - tc.minX);
      }
    }
  }
}

/**
 * 荧光类的 canvas，比如扩增曲线（Quantification）、溶解曲线（Melt）
 *
 * @class
 */
export class FluorescenceCanvas extends DataCanvas {
  constructor(id, initParam = {}) {
    super(id, initParam);

    this.axisNumberBold = Utils.valueOf(initParam.axisNumberBold, false);
    this.axisNumberSize = Utils.valueOf(initParam.axisNumberSize, "16px");
    this.axisNumberFamily = Utils.valueOf(initParam.axisNumberFamily, "Arial");
    this.axisNumberColor = Utils.valueOf(initParam.axisNumberColor, "#000000");
    this.xAxisNumberLength = Utils.valueOf(initParam.xAxisNumberLength, 10);
    this.yAxisNumberLength = Utils.valueOf(initParam.yAxisNumberLength, 5);
    this.xDataStart = Utils.valueOf(initParam.xDataStart, 0);
    this.xDataGradient = Utils.valueOf(initParam.xDataGradient, 1);
    this.xBase1 = Utils.valueOf(initParam.xBase1, false);
    this.autoAxisX = Utils.valueOf(initParam.autoAxisX, true);
    this.autoAxisY = Utils.valueOf(initParam.autoAxisY, true);
    this.lineWidthNormal = Utils.valueOf(initParam.lineWidthNormal, 1);
    this.lineWidthHighlight = Utils.valueOf(initParam.lineWidthHighlight, 3);
    this.showCycleOnLine = Utils.valueOf(initParam.showCycleOnLine, true);
    this.drawLegendMode = Utils.valueOf(initParam.drawLegendMode, false); // 覆盖父类的方法，默认不显示图例
    this.drawLegendBackgroundMode = Utils.valueOf(initParam.drawLegendBackgroundMode, true);
    this.legendBackground = Utils.valueOf(initParam.legendBackground, "#CCCCCC");
    this.circleCrossSizeNormal = Utils.valueOf(initParam.circleCrossSizeNormal, 5);
    this.circleCrossSizeHighlight = Utils.valueOf(initParam.circleCrossSizeHighlight, 10);
    this.maxX = Utils.valueOf(initParam.maxX, 1); // 覆盖父类设置为 Infinity 方法，避免出错，后期可考虑替换父类该方法
    this.drawReferenceLine = Utils.valueOf(initParam.drawReferenceLine, true);
    this.drawLogAxis = Utils.valueOf(initParam.drawLogAxis, false);


    this.lengthY = 1;
    this.lengthX = 1;
    this.scale = 1;
    /** @type {FluorescenceEntity[]} */
    this.entities = [];
    this.__showCycleOnLine = false;
  }

  /**
   * 初始化组件
   *
   * @param {string} id 父容器的 id
   * @param {Object} [initParam] - 初始化参数
   * @param {boolean} [initParam.drawWholeCanvasBackgroundMode] - 是否绘制整个 canvas 的背景色，默认值为 false。
   * @param {string} [initParam.wholeCanvasBackground] - 整个 canvas 的背景色，默认值为 "#FFFFFF"。
   * @param {number} [initParam.paddingTop] - 绘制区域距 canvas 顶部距离，默认值为 0。
   * @param {number} [initParam.paddingBottom] - 绘制区域距 canvas 底部距离，默认值为 0。
   * @param {number} [initParam.paddingLeft] - 绘制区域距 canvas 左边距离，默认值为 0。
   * @param {number} [initParam.paddingRight] - 绘制区域距 canvas 右边距离，默认值为 0。
   * @param {number} [initParam.maxY] - 设置绘制数据的最大值（纵轴），默认值为 Infinity。
   * @param {number} [initParam.minY] - 设置绘制数据的最小值（纵轴），默认值为 0。
   * @param {number} [initParam.maxX] - 设置绘制数据的初始最大值（横轴），默认值为 Infinity。
   * @param {number} [initParam.minX] - 设置绘制数据的最小值（横轴），默认值为 0。
   * @param {number} [initParam.dataPaddingTop] - 绘制数据距绘制区域顶部距离，默认值为 0。
   * @param {number} [initParam.dataPaddingBottom] - 绘制数据距绘制区域底部距离，默认值为 0。
   * @param {number} [initParam.dataPaddingLeft] - 绘制数据距绘制区域左边距离，默认值为 0。
   * @param {number} [initParam.dataPaddingRight] - 绘制数据距绘制区域右边距离，默认值为 0。
   * @param {boolean} [initParam.drawAxisMode] - 是否绘制坐标轴，默认值为 true。
   * @param {AxisType.NORMAL|AxisType.SQUARE|AxisType.NONE} [initParam.axisType] - 坐标轴类型，默认值为 AxisType.SQUARE。
   * @param {BorderStyle.DOTTED|BorderStyle.DASHED|BorderStyle.SOLID} [initParam.axisStyle] - 坐标轴边框类型，默认值为
   *   BorderStyle.SOLID。
   * @param {string} [initParam.axisColor] - 坐标轴颜色，默认值为 "#000000"。
   * @param {number} [initParam.axisLineWidth] - 坐标轴线宽，默认值为 2。
   * @param {boolean} [initParam.drawAxisTitleMode] - 是否绘制坐标轴上轴的标题，默认值为 true。
   * @param {boolean} [initParam.drawAxisNumberMode] - 是否绘制坐标轴上轴的数值标注，默认值为 true。
   * @param {string} [initParam.axisTitleTop] - 横坐标轴的上标题，默认值为 ""。
   * @param {string} [initParam.axisTitleRight] - 纵坐标轴的右标题，默认值为 ""。
   * @param {string} [initParam.axisTitleBottom] - 横坐标轴的下标题，默认值为 ""。
   * @param {string} [initParam.axisTitleLeft] - 纵坐标轴的左标题，默认值为 ""。
   * @param {number} [initParam.axisTitleTopPadding] - 横坐标轴的上标题额外边距，默认值为 0。
   * @param {number} [initParam.axisTitleRightPadding] - 纵坐标轴的右标题额外边距，默认值为 0。
   * @param {number} [initParam.axisTitleBottomPadding] - 横坐标轴的下标题额外边距，默认值为 0。
   * @param {number} [initParam.axisTitleLeftPadding] - 纵坐标轴的左标题额外边距，默认值为 0。
   * @param {boolean} [initParam.axisTitleTopBold] - 坐标轴上标题文字是否加粗，默认值为 false。
   * @param {boolean} [initParam.axisTitleRightBold] - 坐标轴右标题文字是否加粗，默认值为 false。
   * @param {boolean} [initParam.axisTitleBottomBold] - 坐标轴下标题文字是否加粗，默认值为 false。
   * @param {boolean} [initParam.axisTitleLeftBold] - 坐标轴左标题文字是否加粗，默认值为 false。
   * @param {string} [initParam.axisTitleTopSize] - 坐标轴上标题字号，默认值为 "21px"。
   * @param {string} [initParam.axisTitleRightSize] - 坐标轴右标题字号，默认值为 "21px"。
   * @param {string} [initParam.axisTitleBottomSize] - 坐标轴下标题字号，默认值为 "21px"。
   * @param {string} [initParam.axisTitleLeftSize] - 坐标轴左标题字号，默认值为 "21px"。
   * @param {string} [initParam.axisTitleTopFamily] - 坐标轴上标题字体，默认值为 "Arial"。
   * @param {string} [initParam.axisTitleRightFamily] - 坐标轴右标题字体，默认值为 "Arial"。
   * @param {string} [initParam.axisTitleBottomFamily] - 坐标轴下标题字体，默认值为 "Arial"。
   * @param {string} [initParam.axisTitleLeftFamily] - 坐标轴左标题字体，默认值为 "Arial"。
   * @param {string} [initParam.axisTitleTopColor] - 坐标轴上标题颜色，默认值为 "#000000"。
   * @param {string} [initParam.axisTitleRightColor] - 坐标轴右标题颜色，默认值为 "#000000"。
   * @param {string} [initParam.axisTitleBottomColor] - 坐标轴下标题颜色，默认值为 "#000000"。
   * @param {string} [initParam.axisTitleLeftColor] - 坐标轴左标题颜色，默认值为 "#000000"。
   * @param {boolean} [initParam.axisNumberBold] - 坐标轴的坐标标数文字是否加粗，默认值为 false。
   * @param {string} [initParam.axisNumberSize] - 坐标轴的坐标标数文字字号，默认值为 "16px"。
   * @param {string} [initParam.axisNumberFamily] - 坐标轴的坐标标数文字字体，默认值为 "Arial"。
   * @param {string} [initParam.axisNumberColor] - 坐标轴的坐标标数文字颜色，默认值为 "#000000"。
   * @param {number} [initParam.xAxisNumberLength] - 横坐标轴的坐标标数分割数，当且仅当 {@link autoAxisX} 为 false 时有效，默认值为 10。
   * @param {number} [initParam.yAxisNumberLength] - 纵坐标轴的坐标标数分割数，当且仅当 {@link autoAxisY} 为 false 时有效，默认值为 5。
   * @param {Number} [initParam.xDataStart] - 数据在横坐标轴上的起始数，当且仅当 EntityType.type = NORMAL 时使用，用于校正显示位置。默认值为 0。
   * @param {Number} [initParam.xDataGradient] - 数据在横坐标轴上的递增数，当且仅当 EntityType.type = NORMAL 时使用，用于校正显示位置。默认值为 1。
   * @param {boolean} [initParam.xBase1] - 横坐标轴的坐标标数是否 +1，仅针对坐标显示，和绘图无关。默认值为 false。
   * @param {boolean} [initParam.autoAxisX] - 横坐标轴的是否自动扩展计算成整数，默认值为 true。
   * @param {boolean} [initParam.autoAxisY] - 纵坐标轴的是否自动扩展计算成整数，默认值为 true。如果为 false，则需要手动设置 minY 和 maxY
   * @param {number} [initParam.lineWidthNormal] - 正常线宽，默认值为 1。
   * @param {number} [initParam.lineWidthHighlight] - 高亮线宽，默认值为 3。
   * @param {boolean} [initParam.showCycleOnLine] - 当鼠标捕捉荧光线时是否显示 text 和 x 值，默认值为 true。
   * @param {Boolean} [initParam.drawLegendMode] - 是否绘制图例，Standard 图将此属性设置为 true。默认值为 false。
   * @param {Number} [initParam.circleCrossSizeNormal] - 绘制普通状态下的圈圈叉叉的半径大小。默认值为 5。
   * @param {Number} [initParam.circleCrossSizeHighlight] - 绘制高亮状态下的圈圈叉叉的半径大小，Standard 图将此属性设置为 true。默认值为 10。
   * @param {String} [initParam.legendTextColor] - 图例文字颜色，默认值为 "#000000"。
   * @param {Boolean} [initParam.drawLegendBackgroundMode] - 是否绘制图例背景色，默认值为 true。
   * @param {String} [initParam.legendBackground] - 图例背景色，默认值为 "#CCCCCC"。
   * @param {Boolean} [initParam.drawReferenceLine] - 是否绘制参考线，默认值为 true。
   * @param {Boolean} [initParam.drawReferenceLine] - 是否绘制log坐标，默认值为 false。
   * @returns {FluorescenceCanvas}
   */
  static init(id, initParam = {}) {
    let canvas = new FluorescenceCanvas(id, initParam);
    canvas.resize();
    canvas.__animation();
    canvas.onMousemove();
    return canvas;
  }

  /**
   * 实现水平缩放
   *
   * @override
   */
  resize() {
    super.resize(this.scale, this.scale);
  }

  /**
   * @inheritdoc
   */
  refresh() {
    super.refresh();
    __FluorescenceCanvas.fixMaxXY(this);
    if (this.drawAxisMode) {
      this.__drawAxis();
    }
    if (this.drawAxisTitleMode) {
      this.__drawAxisTitle();
    }
    if (this.drawAxisNumberMode) {
      this.__drawAxisNumber();
    }
    if (this.showCycleOnLine && this.__showCycleOnLine) {
      this.__drawCycleOnLine();
    }
    if (this.drawLegendMode) {
      this.__drawLegend();
      this.__drawLegendMessage();
    }
    __FluorescenceCanvas.draw(this);
  }

  /** @inheritDoc */
  clearData() {
    this.scale = 1;
    this.removeAllEntity();
  }

  /** @inheritDoc */
  destroyCanvas() {
    this.canvas.removeEventListener("mousemove", this.__mousemove);
    super.destroyCanvas();
  }

  /**
   * 添加一条线、圆、叉等的数据
   *
   * @param {FluorescenceEntity} [entity]
   * @return {string} - 添加进来的对象id
   */
  addEntity(entity) {
    this.entities[this.entities.length] = entity;
    return entity.id;
  }

  /**
   * 添加多条线、圆、叉等的数据
   *
   * @param {FluorescenceEntity[]} [entities]
   * @return {string[]} - 添加进来的对象id
   */
  addEntities(entities) {
    entities.forEach(it => this.entities[this.entities.length] = it);
    return entities.map(it => it.id);
  }

  /**
   * 通过 id 移除一个 entity
   *
   * @param {string} id
   */
  removeEntityById(id) {
    this.entities.some((entity, index) => entity.id === id && this.entities.splice(index, 1));
  }

  /**
   * 移除指定的 entity
   *
   * @param {FluorescenceEntity} entity
   */
  removeEntity(entity) {
    this.entities.some((_entity, index) => _entity === entity && this.entities.splice(index, 1));
  }

  /** 移除全部的 entity */
  removeAllEntity() {
    this.entities.length = 0;
  }

  /**
   * 通过 id 高亮一个 entity
   *
   * @param {string} id
   */
  highlightEntityById(id) {
    this.entities.some(entity => entity.id === id && (entity.highlight = true));
  }

  /**
   * 高亮指定的 entity
   *
   * @param {FluorescenceEntity} entity
   */
  highlightEntity(entity) {
    entity.highlight = true;
  }

  /** 高亮全部的 entity */
  highlightAllEntity() {
    this.entities.forEach(entity => entity.highlight = true);
  }

  /**
   * 通过 id 反高亮一个 entity
   *
   * @param {string} id
   */
  antiHighlightEntityById(id) {
    this.entities.some(entity => entity.id === id && !(entity.highlight = false));
  }

  /**
   * 反高亮指定的 entity
   *
   * @param {FluorescenceEntity} entity
   */
  antiHighlightEntity(entity) {
    entity.highlight = false;
  }

  /** 反高亮全部的 entity */
  antiHighlightAllEntity() {
    this.entities.forEach(entity => entity.highlight = false);
  }

  /**
   * 通过 id 显示一个 entity
   *
   * @param {string} id
   */
  showEntityById(id) {
    this.entities.some(entity => entity.id === id && (entity.show = true));
  }

  /**
   * 显示指定的 entity
   *
   * @param {FluorescenceEntity} entity
   */
  showEntity(entity) {
    entity.show = true;
  }

  /** 显示全部的 entity */
  showAllEntity() {
    this.entities.forEach(entity => entity.show = true);
  }

  /**
   * 通过 id 隐藏一个 entity
   *
   * @param {string} id
   */
  hideEntityById(id) {
    this.entities.some(entity => entity.id === id && !(entity.show = false));
  }

  /**
   * 隐藏指定的 entity
   *
   * @param {FluorescenceEntity} entity
   */
  hideEntity(entity) {
    entity.show = false;
  }

  /** 隐藏全部的 entity */
  hideAllEntity() {
    this.entities.forEach(entity => entity.show = false);
  }

  __drawAxisTitle() {
    const ctx = this.context;
    ctx.textAlign = "center";
    // 上标题
    if (this.axisTitleTop !== "") {
      ctx.textBaseline = "bottom";
      ctx.font = `${this.axisTitleTopBold ? "bold" : "normal"} ${this.axisTitleTopSize} ${this.axisTitleTopFamily}`;
      ctx.fillStyle = this.axisTitleTopColor;
      ctx.fillText(this.axisTitleTop, (this.left + this.right) * 0.5, this.top - (5 + this.axisTitleTopPadding));
    }
    // 下标题
    if (this.axisTitleBottom !== "") {
      ctx.textBaseline = "top";
      ctx.font =
        `${this.axisTitleBottomBold ? "bold" : "normal"} ${this.axisTitleBottomSize} ${this.axisTitleBottomFamily}`;
      ctx.fillStyle = this.axisTitleBottomColor;
      ctx.fillText(this.axisTitleBottom, (this.left + this.right) * 0.5,
        this.bottom + (5 + this.axisTitleBottomPadding));
    }
    // 右标题
    if (this.axisTitleRight !== "") {
      ctx.textBaseline = "bottom";
      ctx.font =
        `${this.axisTitleRightBold ? "bold" : "normal"} ${this.axisTitleRightSize} ${this.axisTitleRightFamily}`;
      ctx.fillStyle = this.axisTitleRightColor;
      ctx.translate(this.width, this.height * 0.5);
      ctx.rotate(Math.PI * 0.5);
      ctx.fillText(this.axisTitleRight, 0, this.paddingRight - (5 + this.axisTitleRightPadding));
      ctx.rotate(-Math.PI * 0.5);
      ctx.translate(-this.width, -this.height * 0.5);
    }
    // 左标题
    if (this.axisTitleLeft !== "") {
      ctx.textBaseline = "bottom";
      ctx.font = `${this.axisTitleLeftBold ? "bold" : "normal"} ${this.axisTitleLeftSize} ${this.axisTitleLeftFamily}`;
      ctx.fillStyle = this.axisTitleLeftColor;
      ctx.translate(0, this.height * 0.5);
      ctx.rotate(-Math.PI * 0.5);
      ctx.fillText(this.axisTitleLeft, 0, this.paddingLeft - (5 + this.axisTitleLeftPadding));
      ctx.rotate(Math.PI * 0.5);
      ctx.translate(0, -this.height * 0.5);
    }
  }

  __drawAxisNumber() {
    const ctx = this.context;
    ctx.textAlign = "right";
    ctx.textBaseline = "middle";
    ctx.font = `${this.axisNumberBold ? "bold" : "normal"} ${this.axisNumberSize} ${this.axisNumberFamily}`;
    ctx.fillStyle = this.axisNumberColor;
    for (let i = 0; i <= this.lengthY; i++) {
      let y = this.minY + (this.maxY - this.minY) / this.lengthY * i;
      y = y > 1 ? Math.round(y) : y;  //  加了此判断,把大于1的值取整
      //  TODO: y轴显示数据过长进行截取
      if ((y + "").length > 6) {
        typeof y === "number" && (y = y.toFixed(3));
      }
      //  TODO: log图,自定义y轴坐标显示
      if (this.drawLogAxis && Math.round(y) === y) {
        ctx.fillText(Math.pow(10, y) + "", this.left - 5, this.getY(y));
      } else if (!this.drawLogAxis) {
        ctx.fillText(y + "", this.left - 5, this.getY(y));
      }
      //  绘制参考线
      if (this.drawReferenceLine) {
        ctx.lineWidth=1
        ctx.strokeStyle = "#ccc";
        ctx.beginPath();
        ctx.setLineDash([5, 5]);
        ctx.moveTo(this.left, this.getY(y));
        ctx.lineTo(this.right, this.getY(y));
        ctx.stroke();
        ctx.setLineDash([0]);
      }
    }

    ctx.textAlign = "center";
    ctx.textBaseline = "top";
    for (let i = 0; i <= this.lengthX; i++) {
      let x = this.minX + (this.maxX - this.minX) / this.lengthX * i;
      x = x .toFixed(1)/1;
      ctx.fillText((this.xBase1 ? x + 1 : x) + "", this.getX(x), this.bottom + 5);
      //  绘制参考线
      if (this.drawReferenceLine) {
        ctx.lineWidth=1
        ctx.strokeStyle = "#ccc";
        ctx.beginPath();
        ctx.setLineDash([5, 5]);
        ctx.moveTo(this.getX(x), this.bottom);
        ctx.lineTo(this.getX(x), this.top);
        ctx.stroke();
        ctx.setLineDash([0]);
      }
    }
  }

  __drawCycleOnLine() {
    this.context.fillStyle = this.__cycleOnLineColor;
    this.context.textAlign = "right";
    this.context.textBaseline = "bottom";
    this.context.fillText(this.__cycleOnLine, this.__cycleOnLineX, this.__cycleOnLineY);
  }

  __drawLegend() {
    const w = 115, h = 65,
      x1 = this.right - w - 5.5, y1 = this.top + 5.5,  // 左上角点
      x2 = x1 + w, y2 = y1 + h, // 右下角点
      ctx = this.context;
    this.__legendCommonFunction__(ctx, x1, x2, y1, y2, w, h);
    ctx.textAlign = "left";
    ctx.textBaseline = "middle";
    // Standard
    ctx.beginPath();
    ctx.arc(x1 + 20, y1 + 20, 8, -Math.PI, Math.PI);
    ctx.stroke();
    ctx.fillStyle = this.legendTextColor;
    ctx.fillText("Standard", x1 + 35, y1 + 21);
    // Unknown
    ctx.beginPath();
    ctx.moveTo(x1 + 13, y1 + 13 + 25);
    ctx.lineTo(x1 + 27, y1 + 27 + 25);
    ctx.moveTo(x1 + 27, y1 + 13 + 25);
    ctx.lineTo(x1 + 13, y1 + 27 + 25);
    ctx.fillText("Unknown", x1 + 35, y1 + 21 + 25);
    ctx.stroke();
  }

  __drawLegendMessage() {
    const lineEntities = this.entities.filter(it => it.type === EntityType.LINE && it.show);
    if (lineEntities.length === 0) {
      return;
    }
    const nameMaxWidth = Math.max(...lineEntities.map(it => this.context.measureText(it.name).width)),
      textMaxWidth = Math.max(...lineEntities.map(it => this.context.measureText(it.text).width)),
      count = lineEntities.length;
    const w = 45 + nameMaxWidth + 10 + textMaxWidth, h = 15 + count * 25 - Math.min(count - 1, 1) * 5,
      x1 = this.left + 5.5, y1 = this.bottom - h - 5.5,  // 左上角点
      x2 = x1 + w, y2 = y1 + h, // 右下角点
      ctx = this.context;
    this.__legendCommonFunction__(ctx, x1, x2, y1, y2, w, h);
    ctx.textBaseline = "middle";
    ctx.textAlign = "left";
    // 遍历写每一条线的信息
    lineEntities.forEach((it, index) => {
      ctx.strokeStyle = it.color;
      ctx.beginPath();
      ctx.moveTo(x1 + 10, y1 + 20 + index * 25);
      ctx.lineTo(x1 + 35, y1 + 20 + index * 25);
      ctx.stroke();
      ctx.fillStyle = it.nameColor;
      ctx.fillText(it.name, x1 + 40, y1 + 21 + index * 25);
      ctx.fillStyle = it.textColor;
      ctx.fillText(it.text, x1 + 40 + nameMaxWidth + 5, y1 + 21 + index * 25);
    });
  }

  __legendCommonFunction__(ctx, x1, x2, y1, y2, w, h) {
    // 首先清空区域
    ctx.clearRect(x1, y1, w, h);
    // 依据模式填充背景
    if (this.drawWholeCanvasBackgroundMode) {
      ctx.fillStyle = this.wholeCanvasBackground;
      ctx.fillRect(x1, y1, w, h);
    }
    if (this.drawLegendBackgroundMode) {
      ctx.fillStyle = this.legendBackground;
      ctx.fillRect(x1, y1, w, h);
    }
    // 将图例圈起来
    ctx.lineWidth = 1;
    ctx.strokeStyle = this.legendTextColor;
    ctx.beginPath();
    ctx.moveTo(x1, y1);
    ctx.lineTo(x2, y1);
    ctx.lineTo(x2, y2);
    ctx.lineTo(x1, y2);
    ctx.closePath();
    ctx.stroke();
  }

  setAxisNumberBold(axisNumberBold) {
    this.axisNumberBold = Utils.valueOf(axisNumberBold, false);
  }

  setAxisNumberSize(axisNumberSize) {
    this.axisNumberSize = Utils.valueOf(axisNumberSize, "16px");
  }

  setAxisNumberFamily(axisNumberFamily) {
    this.axisNumberFamily = Utils.valueOf(axisNumberFamily, "Arial");
  }

  setAxisNumberColor(axisNumberColor) {
    this.axisNumberColor = Utils.valueOf(axisNumberColor, "#000000");
  }

  setXAxisNumberLength(xAxisNumberLength) {
    this.xAxisNumberLength = Utils.valueOf(xAxisNumberLength, 10);
  }

  setYAxisNumberLength(yAxisNumberLength) {
    this.yAxisNumberLength = Utils.valueOf(yAxisNumberLength, 5);
  }

  setXDataStart(xDataStart) {
    this.xDataStart = Utils.valueOf(xDataStart, 0);
  }

  setXDataGradient(xDataGradient) {
    this.xDataGradient = Utils.valueOf(xDataGradient, 1);
  }

  setXBase1(xBase1) {
    this.xBase1 = Utils.valueOf(xBase1, false);
  }

  setAutoAxisX(autoAxisX) {
    this.autoAxisX = Utils.valueOf(autoAxisX, true);
  }

  setAutoAxisY(autoAxisY) {
    this.autoAxisY = Utils.valueOf(autoAxisY, true);
  }

  setLineWidthNormal(lineWidthNormal) {
    this.lineWidthNormal = Utils.valueOf(lineWidthNormal, 1);
  }

  setLineWidthHighlight(lineWidthHighlight) {
    this.lineWidthHighlight = Utils.valueOf(lineWidthHighlight, 3);
  }

  setShowCycleOnLine(showCycleOnLine) {
    this.showCycleOnLine = Utils.valueOf(showCycleOnLine, true);
  }

  setDrawLegendMode(drawLegendMode) {
    this.drawLegendMode = Utils.valueOf(drawLegendMode, false);
  }

  setDrawLegendBackgroundMode(drawLegendBackgroundMode) {
    this.drawLegendBackgroundMode = Utils.valueOf(drawLegendBackgroundMode, true);
  }

  setLegendBackground(legendBackground) {
    this.legendBackground = Utils.valueOf(legendBackground, "#CCCCCC");
  }

  setDrawLogAxis(drawLogAxis) {
    this.drawLogAxis = Utils.valueOf(drawLogAxis, false);
  }

  // 可被调用的鼠标移动监听，参数为一个回调函数，this 指针指向被捕获到的对象，没捕获到则指向 undefined
  onMousemove(fn = () => undefined) {
    this.canvas.removeEventListener("mousemove", this.__mousemove);
    this.__mousemove = e => {

      let [eX, eY] = [e.offsetX, e.offsetY];
      let showEntities = this.entities.filter(it => it.show);
      let entity = showEntities.find( // 优先捕获高亮的阈值线
        it => it.type === EntityType.THRESHOLD
          && it.highlight
          && Utils.isMid(it.dataList[0], this.minY, this.maxY)  // 如果不在 minY 和 maxY 中则不会绘制，也就不捕获
          && this.nearY(it.dataList[0], eY)
      ) || showEntities.find( // 优先捕获阈值线
        it => it.type === EntityType.THRESHOLD
          && Utils.isMid(it.dataList[0], this.minY, this.maxY)  // 如果不在 minY 和 maxY 中则不会绘制，也就不捕获
          && this.nearY(it.dataList[0], eY)
      ) || showEntities.find(it => {  // 其次再捕获非阈值线
        switch (it.type) {
          case EntityType.NORMAL:
            return it.dataList.some((data, index) =>
              this.nearX(this.xDataStart + index * this.xDataGradient, eX)  // melt 类型 x 坐标变更的特殊处理
              && this.nearY(Utils.mid(data, this.minY, this.maxY), eY)); // 自定义 minY 和 maxY 的特殊处理;
          case EntityType.CIRCLE:
          case EntityType.CROSS:
            return this.nearX(it.singleDataX, eX, this.circleCrossSizeHighlight)
              && this.nearY(it.dataList[0], eY, this.circleCrossSizeHighlight);
          default:
            return false;
        }
      });

      this.__showCycleOnLine = false;
      this.antiHighlightAllEntity();
      if (entity !== undefined) {
        switch (entity.type) {
          case  EntityType.THRESHOLD:
            e.buttons === 1 && (entity.dataList[0] = this.getRealY(eY));
            break;
          case EntityType.NORMAL:
            this.__showCycleOnLine = true;
            this.__cycleOnLineColor = entity.textColor;
            this.__cycleOnLine = `${entity.text}: ${this.getRealX(eX).toFixed(2)} `;
            this.__cycleOnLineX = eX;
            this.__cycleOnLineY = eY;
            break;
        }
        this.highlightEntity(entity);
      }
      fn.call(entity, e);
    };
    this.canvas.addEventListener("mousemove", this.__mousemove);
  }
}

class __FluorescenceCanvas {
  /** @param {FluorescenceCanvas} fc */
  static draw(fc) {
    const showEntities = fc.entities.filter(entity => entity.show);
    // 绘制非高亮普通荧光曲线
    showEntities.forEach(it => it.type === EntityType.NORMAL && !it.highlight && draw(it, fc.lineWidthNormal));
    // 绘制高亮普通荧光曲线
    showEntities.forEach(it => it.type === EntityType.NORMAL && it.highlight && draw(it, fc.lineWidthHighlight));
    // 绘制标准曲线图中的直线
    showEntities.forEach(it => it.type === EntityType.LINE && draw(it, fc.lineWidthNormal));
    // 绘制非高亮圈圈叉叉
    showEntities.forEach(it => (it.type === EntityType.CIRCLE || it.type === EntityType.CROSS) && !it.highlight
      && draw(it, fc.lineWidthNormal, fc.circleCrossSizeNormal));
    // 绘制高亮圈圈叉叉
    showEntities.forEach(it => (it.type === EntityType.CIRCLE || it.type === EntityType.CROSS) && it.highlight
      && draw(it, fc.lineWidthHighlight, fc.circleCrossSizeHighlight));
    // 绘制阈值非高亮线
    showEntities.forEach(it => it.type === EntityType.THRESHOLD && !it.highlight && draw(it, fc.lineWidthNormal));
    // 绘制阈值高亮线
    showEntities.forEach(it => it.type === EntityType.THRESHOLD && it.highlight && draw(it, fc.lineWidthHighlight));

    // 绘制具体内容的方法
    function draw(entity, lineWidth, size) {
      fc.context.strokeStyle = entity.color;
      fc.context.lineWidth = lineWidth;
      fc.context.beginPath();
      switch (entity.type) {
        case EntityType.NORMAL:
          fc.context.moveTo(fc.getX(fc.xDataStart), fc.getY(Utils.mid(entity.dataList[0], fc.minY, fc.maxY)));
          entity.dataList.forEach((data, index) => fc.context.lineTo(
            fc.getX(fc.xDataStart + index * fc.xDataGradient),
            fc.getY(Utils.mid(data, fc.minY, fc.maxY))
          ));
          break;
        case EntityType.THRESHOLD:
          if (Utils.notMid(entity.dataList[0], fc.minY, fc.maxY)) {
            return;
          }
          fc.context.moveTo(fc.left + fc.dataPaddingLeft * 0.5, fc.getY(entity.dataList[0]));
          fc.context.lineTo(fc.right - fc.dataPaddingRight * 0.5, fc.getY(entity.dataList[0]));
          break;
        case EntityType.CIRCLE:
          if (Utils.notMid(entity.singleDataX, fc.minX, fc.maxX)
            || Utils.notMid(entity.dataList[0], fc.minY, fc.maxY)) {
            return;
          }
          fc.context.arc(fc.getX(entity.singleDataX), fc.getY(entity.dataList[0]), size, 0, 2 * Math.PI);
          break;
        case EntityType.CROSS:
          if (Utils.notMid(entity.singleDataX, fc.minX, fc.maxX)
            || Utils.notMid(entity.dataList[0], fc.minY, fc.maxY)) {
            return;
          }
          const x = fc.getX(entity.singleDataX), y = fc.getY(entity.dataList[0]);
          fc.context.moveTo(x - size, y - size);
          fc.context.lineTo(x + size, y + size);
          fc.context.moveTo(x - size, y + size);
          fc.context.lineTo(x + size, y - size);
          break;
        case EntityType.LINE:
          const topX = (fc.maxY - entity.b) / entity.k,
            bottomX = (fc.minY - entity.b) / entity.k,
            leftY = entity.k * fc.minX + entity.b,
            rightY = entity.k * fc.maxX + entity.b;
          fc.context.moveTo(...getXY(leftY, topX, bottomX, fc.dataLeft));
          fc.context.lineTo(...getXY(rightY, topX, bottomX, fc.dataRight));
          break;
      }
      fc.context.stroke();
    }

    // 取不超过数据绘制区域的 x、y 值
    function getXY(y, topX, bottomX, x) {
      if (y > fc.maxY) {
        return [fc.getX(topX), fc.dataTop];
      } else if (y < fc.minY) {
        return [fc.getX(bottomX), fc.dataBottom];
      } else {
        return [x, fc.getY(y)];
      }
    }
  }

  /**
   * 校正 最值
   *
   * @param {FluorescenceCanvas} [fc]
   */
  static fixMaxXY(fc) {
    // console.log(fc);
    if (fc.entities.length === 0) {
      return;
    }

    if (fc.autoAxisX) {
      let fineTuning; // 不需要赋初值，fix 方法会自动赋初值
      if (Math.abs(fc.maxX - fc.minX) <= 5) {
        fineTuning = 10;
      } else if (Math.abs(fc.maxX - fc.minX) <= 10) {
        fineTuning = 5;
      }
      fix("maxX", "minX", "lengthX", fineTuning);
    } else {
      fc.lengthX = fc.xAxisNumberLength;
    }

    if (fc.autoAxisY) {
      [fc.minY, fc.maxY] = [Infinity, -Infinity];
      fc.entities.forEach(entity => {
        if (!entity.show || entity.dataList.length === 0) {
          return;
        }
        fc.maxY = Math.max(...entity.dataList, fc.maxY);
        fc.minY = Math.min(...entity.dataList, fc.minY);
      });
      if (fc.minY === Infinity && fc.maxY === -Infinity) {
        [fc.minY, fc.maxY] = [0, 1];
      }/* else if (isNaN(fc.maxY)) {   //datalist传过来为NAN时,扩增曲线rawdata会消失,排查后台阈值计算错误,(暂隐掉,此代码至少显示出来固定值)
        [fc.minY, fc.maxY] = [0, 150];
      }*/ else {
        let fineTuning; // 不需要赋初值，fix 方法会自动赋初值
        if (Math.abs(fc.maxY - fc.minY) <= 5) {
          fineTuning = 10;
        } else if (Math.abs(fc.maxY - fc.minY) <= 10) {
          fineTuning = 5;
        }
        fix("maxY", "minY", "lengthY", fineTuning);
      }
    } else {
      fc.lengthY = fc.yAxisNumberLength;
    }

    fc.coeY = fc.dataContentHeight / (fc.maxY - fc.minY);
    fc.coeX = fc.dataContentWidth / (fc.maxX - fc.minX);

    /**
     * 校正 最值 核心方法
     *
     * @param {String} [max] - 属性名
     * @param {String} [min] - 属性名
     * @param {String} [length] - 属性名
     * @param {number} [fineTuning] - 对于 max、min 差值过小的情况下，length 会计算成 1，通过该参数弥补算法上的缺陷
     */
    function fix(max, min, length, fineTuning = 1) {
      fc[max] *= fineTuning;
      fc[min] *= fineTuning;

      let _max = Utils.getNumOfIntPart(fc[max]);
      let _min = Utils.getNumOfIntPart(fc[min]);

      let _diff = Utils.getNumOfIntPart(fc[max] - fc[min]);
      let e = _max.e / _min.e;
      if (_diff.ceil <= 50) {
        fc[max] = Math.ceil(_max.ceil / 5) * 5 * _max.e;
        fc[min] = Math.floor((_min.floor / e) / 5) * 5 * _max.e;
        fc[length] = Math.ceil(_diff.ceil / 5);
      } else {
        fc[max] = Math.ceil(_max.ceil / 10) * 10 * _max.e;
        fc[min] = Math.floor((_min.floor / e) / 10) * 10 * _max.e;
        fc[length] = Math.ceil(_diff.ceil / 10);
      }
      let now = Date.now(); // 防止死循环，后期考虑优化，避免死循环
      while ((fc[max] - fc[min]) % fc[length] !== 0 && Date.now() - now < 10) {
        fc[length]++;
      }

      fc[max] /= fineTuning;
      fc[min] /= fineTuning;
    }
  }
}

export class FluorescenceEntity {
  constructor() {
    /** @type {string} 唯一 id，好像只在 xxxById 时使用 */
    this.id = Utils.generateUUID();
    /** @type {string} */
    this.color = "#000000";
    /** @type {boolean} */
    this.show = true;
    /** @type {boolean} */
    this.highlight = false;
    /** @type {number[]} 无论 type 是什么，该参数仅能是 y 值，因为涉及到了最值的计算 */
    this.dataList = [];
    /** @type {number} 当且仅当 type 为 CIRCLE 或 CROSS 时使用 */
    this.singleDataX = 0;
    /** @type {number} 当且仅当 type 为 LINE 时使用 */
    this.k = -1;
    /** @type {number} 当且仅当 type 为 LINE 时使用 */
    this.b = 0;
    /** @type {EntityType.NORMAL|EntityType.THRESHOLD|EntityType.CIRCLE|EntityType.CROSS|EntityType.LINE} */
    this.type = EntityType.NORMAL;
    /** @type {string}
     * 1. 在 type = NORMAL 时，表示鼠标捕捉阈值线时显示文本的前半部分，如 "A1"
     * 2. 在 type = LINE 时，表示 Standard 最下角图例的公式，如 "E=100% R^2=1 y=kx+b"
     */
    this.text = "";
    /** @type {string} 对应 text 的颜色*/
    this.textColor = "#000000";
    /** @type {string} target name, 仅在 type = LINE 时使用 */
    this.name = "";
    /** @type {string} 对应 name 的颜色*/
    this.nameColor = "#000000";
  }

  // 一定要使用该方法获取对象，否则可能会丢失参数
  static getInstance(obj) {
    return {...new FluorescenceEntity(), ...obj};
  }
}

export const EntityType = {
  NORMAL: {}, // 普通的荧光曲线：dataList 表示多点的 y 值。（默认值）
  THRESHOLD: {},  // 阈值线（平行于 x 轴的线）：dataList 长度为 1，元素表示阈值
  CIRCLE: {}, // 圆圈：dataList 长度为 1，元素表示 y 值，需要 singleDataX 参数作为 x 值
  CROSS: {},  // 叉叉：dataList 长度为 1，元素表示 y 值，需要 singleDataX 参数作为 x 值
  LINE: {}   // 一条非平行于 x 轴的线：仅使用 k(≠0)、b 两个参数（y = kx + b）
};

class Utils {
  /**
   * 确保数据不是 undefined 或 null，如果是则返回第二个参数
   *
   * @value {*} value
   * @value {*} defaultValue
   * @returns {*}
   */
  static valueOf(value, defaultValue) {
    if (value === undefined || value === null) {
      return defaultValue;
    }
    return value;
  }

  static generateUUID() {
    let d = Date.now();
    if (window.performance && typeof window.performance.now === "function") {
      d += performance.now(); // use high-precision timer if available
    }
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
      let r = (d + Math.random() * 16) % 16 | 0;
      d = Math.floor(d / 16);
      return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
    });
  }

  static getNumOfIntPart(number) {
    let obj = {length: (Math.abs(number) + "").split(".")[0].length};
    if (obj.length < 3) {
      obj.e = 1;
      obj.ceil = Math.ceil(number);
      obj.floor = Math.floor(number);
    } else {
      obj.e = 10 ** (obj.length - 2);
      let num = number / obj.e;
      obj.ceil = Math.ceil(num);
      obj.floor = Math.floor(num);
    }
    return obj;
  }

  static mid(x, min, max) {
    return Math.min(Math.max(x, min), max);
  }

  static isMid(x, min, max) {
    return min <= x && x <= max;
  }

  static notMid(x, min, max) {
    return !this.isMid(x, min, max);
  }
}
